ConversationItemV2 Quote support and various fixes.

This commit is contained in:
Alex Hart
2023-08-31 16:31:21 -03:00
committed by Nicholas Tinsley
parent 153d3ad388
commit 540a2b1876
12 changed files with 200 additions and 54 deletions

View File

@@ -246,11 +246,6 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser
public void onRecipientChanged(@NonNull Recipient recipient) {
setQuoteAuthor(recipient);
}
public @NonNull Projection getProjection(@NonNull ViewGroup parent) {
return Projection.relativeToParent(parent, this, getCorners());
}
public @NonNull Projection.Corners getCorners() {
return new Projection.Corners(cornerMask.getRadii());
}

View File

@@ -6,6 +6,8 @@
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
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaOutgoingBinding
import org.thoughtcrime.securesms.util.views.Stub
@@ -18,7 +20,9 @@ import org.thoughtcrime.securesms.util.views.Stub
*/
data class V2ConversationItemMediaBindingBridge(
val textBridge: V2ConversationItemTextOnlyBindingBridge,
val thumbnailStub: Stub<ImageView>
val thumbnailStub: Stub<ImageView>,
val quoteStub: Stub<QuoteView>,
val bodyContentSpacer: Space
)
/**
@@ -45,7 +49,9 @@ fun V2ConversationItemMediaIncomingBinding.bridge(): V2ConversationItemMediaBind
return V2ConversationItemMediaBindingBridge(
textBridge = textBridge,
thumbnailStub = Stub(conversationItemThumbnailStub)
thumbnailStub = Stub(conversationItemThumbnailStub),
quoteStub = Stub(conversationItemQuoteStub),
bodyContentSpacer = conversationItemContentSpacer
)
}
@@ -73,6 +79,8 @@ fun V2ConversationItemMediaOutgoingBinding.bridge(): V2ConversationItemMediaBind
return V2ConversationItemMediaBindingBridge(
textBridge = textBridge,
thumbnailStub = Stub(conversationItemThumbnailStub)
thumbnailStub = Stub(conversationItemThumbnailStub),
quoteStub = Stub(conversationItemQuoteStub),
bodyContentSpacer = conversationItemContentSpacer
)
}

View File

@@ -6,18 +6,24 @@
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
import org.thoughtcrime.securesms.util.isStoryReaction
import org.thoughtcrime.securesms.util.visible
/**
* Represents a media-backed conversation item.
@@ -43,6 +49,76 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
conversationMessage = (model as ConversationMessageElement).conversationMessage
presentThumbnail()
super.bind(model)
presentQuote()
updateMediaConstraints()
}
private fun updateMediaConstraints() {
binding.bodyContentSpacer.visible = (hasGroupSenderName() && hasThumbnail()) || hasQuote()
binding.textBridge.root.changeConstraints {
val maxBodyWidth = if (hasThumbnail()) {
thumbnailSize[0]
} else {
0
}
this.constrainMaxWidth(binding.textBridge.conversationItemBodyWrapper.id, maxBodyWidth)
}
}
private fun presentQuote() {
val record = requireMediaMessage()
val quote = record.quote
if (quote == null) {
binding.quoteStub.visibility = View.GONE
return
}
val quoteView = binding.quoteStub.get()
quoteView.setOnClickListener {
conversationContext.clickListener.onQuoteClicked(record)
}
binding.quoteStub.visibility = View.VISIBLE
quoteView.setQuote(
conversationContext.glideRequests,
quote.id,
Recipient.live(quote.author).get(),
quote.displayText,
quote.isOriginalMissing,
quote.attachment,
if (conversationMessage.messageRecord.isStoryReaction()) conversationMessage.messageRecord.body else null,
quote.quoteType
)
quoteView.setMessageType(
when {
conversationMessage.messageRecord.isStoryReaction() && binding.textBridge.isIncoming -> QuoteView.MessageType.STORY_REPLY_INCOMING
conversationMessage.messageRecord.isStoryReaction() -> QuoteView.MessageType.STORY_REPLY_OUTGOING
binding.textBridge.isIncoming -> QuoteView.MessageType.INCOMING
else -> QuoteView.MessageType.OUTGOING
}
)
quoteView.setWallpaperEnabled(conversationContext.hasWallpaper())
quoteView.setTextSize(TypedValue.COMPLEX_UNIT_SP, SignalStore.settings().getMessageQuoteFontSize(context).toFloat())
val isOutgoing = conversationMessage.messageRecord.isOutgoing
when (shape) {
V2ConversationItemShape.MessageShape.SINGLE, V2ConversationItemShape.MessageShape.START -> {
val isGroupThread = conversationMessage.threadRecipient.isGroup
quoteView.setTopCornerSizes(
isOutgoing || !isGroupThread,
isOutgoing || !isGroupThread
)
}
V2ConversationItemShape.MessageShape.MIDDLE, V2ConversationItemShape.MessageShape.END -> {
quoteView.setTopCornerSizes(isOutgoing, !isOutgoing)
}
}
}
private fun presentThumbnail() {
@@ -52,9 +128,6 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
thumbnailSize[0] = -1
thumbnailSize[1] = -1
binding.textBridge.root.changeConstraints {
this.constrainMaxWidth(binding.textBridge.conversationItemBodyWrapper.id, 0)
}
return
}
@@ -98,10 +171,6 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
height = thumbnailSize[1]
}
binding.textBridge.root.changeConstraints {
this.constrainMaxWidth(binding.textBridge.conversationItemBodyWrapper.id, thumbnailSize[0])
}
if (thumbnailBlur != null) {
val placeholderTarget = PlaceholderTarget(binding.thumbnailStub.get())
conversationContext
@@ -174,6 +243,22 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
)
}
private fun hasGroupSenderName(): Boolean {
return binding.textBridge.senderName?.visible == true
}
private fun hasThumbnail(): Boolean {
return binding.thumbnailStub.isVisible
}
private fun hasQuote(): Boolean {
return binding.quoteStub.isVisible
}
private fun hasMedia(): Boolean {
return hasThumbnail() || hasQuote()
}
private fun isThumbnailMetricsSatisfied(maxWidth: Int, maxHeight: Int): Boolean {
return thumbnailSize[0] in 1..maxWidth && thumbnailSize[1] in 1..maxHeight
}

View File

@@ -162,6 +162,9 @@ class V2ConversationItemShape(
/**
* This message is in the middle of a cluster
*/
MIDDLE(collapsedSpacing, collapsedSpacing)
MIDDLE(collapsedSpacing, collapsedSpacing);
val isStartingShape: Boolean get() = this == SINGLE || this == START
val isEndingShape: Boolean get() = this == SINGLE || this == END
}
}

View File

@@ -100,6 +100,8 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
private val bodyBubbleDrawable = ChatColorsDrawable()
private val footerDrawable = ChatColorsDrawable()
protected lateinit var shape: V2ConversationItemShape.MessageShape
init {
binding.root.addOnMeasureListener(footerDelegate)
binding.root.onDispatchTouchEventListener = dispatchTouchEventListener
@@ -152,7 +154,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
check(model is ConversationMessageElement)
conversationMessage = model.conversationMessage
val shape = shapeDelegate.setMessageShape(
shape = shapeDelegate.setMessageShape(
isLtr = itemView.layoutDirection == View.LAYOUT_DIRECTION_LTR,
currentMessage = conversationMessage.messageRecord,
isGroupThread = conversationMessage.threadRecipient.isGroup,
@@ -160,10 +162,10 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
)
presentBody()
presentDate(shape)
presentDeliveryStatus(shape)
presentFooterBackground(shape)
presentFooterExpiry(shape)
presentDate()
presentDeliveryStatus()
presentFooterBackground()
presentFooterExpiry()
presentAlert()
presentSender()
presentReactions()
@@ -204,7 +206,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
Projection.relativeToParent(
coordinateRoot,
binding.conversationItemBodyWrapper,
Projection.Corners.NONE
shapeDelegate.corners
).translateX(binding.conversationItemBodyWrapper.translationX).translateY(root.translationY)
)
@@ -397,7 +399,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
return conversationContext.displayMode == ConversationItemDisplayMode.CONDENSED && conversationContext.getPreviousMessage(bindingAdapterPosition) == null
}
private fun presentFooterExpiry(shape: V2ConversationItemShape.MessageShape) {
private fun presentFooterExpiry() {
if (shape == V2ConversationItemShape.MessageShape.MIDDLE || shape == V2ConversationItemShape.MessageShape.START) {
binding.conversationItemFooterExpiry.stopAnimation()
binding.conversationItemFooterExpiry.visible = false
@@ -434,14 +436,27 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
if (conversationMessage.threadRecipient.isGroup) {
val sender = conversationMessage.messageRecord.fromRecipient
binding.senderName.visible = true
binding.senderPhoto.visible = true
binding.senderBadge.visible = true
binding.senderName.visible = shape.isStartingShape
val photoVisibility = if (shape.isEndingShape) {
View.VISIBLE
} else {
View.INVISIBLE
}
binding.senderPhoto.visibility = photoVisibility
binding.senderBadge.visibility = photoVisibility
binding.senderName.text = sender.getDisplayName(context)
binding.senderName.setTextColor(conversationContext.getColorizer().getIncomingGroupSenderColor(context, sender))
binding.senderPhoto.setAvatar(conversationContext.glideRequests, sender, false)
binding.senderBadge.setBadgeFromRecipient(sender, conversationContext.glideRequests)
binding.senderPhoto.setOnClickListener {
conversationContext.clickListener.onGroupMemberClicked(
conversationMessage.messageRecord.fromRecipient.id,
conversationMessage.threadRecipient.requireGroupId()
)
}
} else {
binding.senderName.visible = false
binding.senderPhoto.visible = false
@@ -484,7 +499,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
}
}
private fun presentFooterBackground(shape: V2ConversationItemShape.MessageShape) {
private fun presentFooterBackground() {
if (!binding.conversationItemBody.isJumbomoji ||
!conversationContext.hasWallpaper() ||
shape == V2ConversationItemShape.MessageShape.MIDDLE ||
@@ -505,7 +520,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
)
}
private fun presentDate(shape: V2ConversationItemShape.MessageShape) {
private fun presentDate() {
if (shape == V2ConversationItemShape.MessageShape.MIDDLE || shape == V2ConversationItemShape.MessageShape.START) {
binding.conversationItemFooterDate.visible = false
return
@@ -539,7 +554,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
}
}
private fun presentDeliveryStatus(shape: V2ConversationItemShape.MessageShape) {
private fun presentDeliveryStatus() {
val deliveryStatus = binding.conversationItemDeliveryStatus ?: return
if (shape == V2ConversationItemShape.MessageShape.MIDDLE || shape == V2ConversationItemShape.MessageShape.START) {

View File

@@ -55,7 +55,7 @@
<!-- Body -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/conversation_item_body_wrapper"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="48dp"
@@ -66,14 +66,13 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/contact_photo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginStart="16dp"
tools:background="@color/black">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/group_message_sender"
style="@style/TextAppearance.Signal.Subtitle"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginEnd="4sp"
@@ -84,30 +83,50 @@
android:paddingEnd="@dimen/message_bubble_horizontal_padding"
android:textColor="@color/signal_text_primary"
android:textStyle="bold"
app:layout_constraintBaseline_toTopOf="@id/conversation_item_thumbnail_stub"
app:layout_constraintBottom_toTopOf="@id/conversation_item_content_spacer"
app:layout_constraintEnd_toEndOf="@id/conversation_item_body"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="+14152222222"
tools:visibility="visible" />
<Space
android:id="@+id/conversation_item_content_spacer"
android:layout_width="match_parent"
android:layout_height="@dimen/message_bubble_top_padding"
app:layout_constraintBottom_toTopOf="@id/conversation_item_quote_stub"
app:layout_constraintTop_toBottomOf="@id/group_message_sender" />
<ViewStub
android:id="@+id/conversation_item_quote_stub"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:inflatedId="@id/conversation_item_quote_stub"
android:layout="@layout/v2_conversation_item_quote_stub"
app:layout_constraintBottom_toTopOf="@id/conversation_item_thumbnail_stub"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_item_content_spacer"
app:layout_constraintWidth_default="spread"
app:layout_goneMarginTop="0dp" />
<!-- Media content goes here -->
<ViewStub
android:id="@+id/conversation_item_thumbnail_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:inflatedId="@id/conversation_item_thumbnail_stub"
android:layout="@layout/v2_conversation_item_thumbnail_stub"
app:layout_constraintBottom_toTopOf="@id/conversation_item_body"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/group_message_sender"
app:layout_goneMarginTop="0dp" />
app:layout_constraintTop_toBottomOf="@id/conversation_item_quote_stub" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/conversation_item_body"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
android:layout_marginTop="@dimen/message_bubble_top_padding"
@@ -126,7 +145,6 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_item_thumbnail_stub"
app:layout_constraintWidth_default="wrap"
app:measureLastLine="true"
app:scaleEmojis="true"
tools:text="Testy test test test" />

View File

@@ -30,7 +30,7 @@
<!-- Body -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/conversation_item_body_wrapper"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
app:layout_constrainedWidth="true"
@@ -38,25 +38,46 @@
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginEnd="16dp"
tools:background="@color/black">
<android.widget.Space
android:id="@+id/conversation_item_content_spacer"
android:layout_width="match_parent"
android:layout_height="@dimen/message_bubble_top_padding"
app:layout_constraintBottom_toTopOf="@id/conversation_item_quote_stub"
app:layout_constraintTop_toTopOf="parent" />
<ViewStub
android:id="@+id/conversation_item_quote_stub"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:inflatedId="@id/conversation_item_quote_stub"
android:layout="@layout/v2_conversation_item_quote_stub"
app:layout_constraintBottom_toTopOf="@id/conversation_item_thumbnail_stub"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_item_content_spacer"
app:layout_constraintWidth_default="spread" />
<!-- Media content goes here -->
<ViewStub
android:id="@+id/conversation_item_thumbnail_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inflatedId="@id/conversation_item_thumbnail_stub"
android:layout="@layout/v2_conversation_item_thumbnail_stub"
app:layout_constraintBottom_toTopOf="@id/conversation_item_body"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_item_quote_stub"
app:layout_goneMarginTop="0dp" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/conversation_item_body"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
android:layout_marginTop="@dimen/message_bubble_top_padding"
@@ -75,7 +96,6 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_item_thumbnail_stub"
app:layout_constraintWidth_default="wrap"
app:measureLastLine="true"
app:scaleEmojis="true"
tools:text="Testy test test test" />

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<org.thoughtcrime.securesms.components.QuoteView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

View File

@@ -55,7 +55,7 @@
<!-- Body -->
<LinearLayout
android:id="@+id/conversation_item_body_wrapper"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="48dp"
@@ -66,7 +66,6 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/contact_photo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginStart="16dp"
tools:background="@color/black">

View File

@@ -30,7 +30,7 @@
<!-- Body -->
<FrameLayout
android:id="@+id/conversation_item_body_wrapper"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
app:cardElevation="0dp"
@@ -39,7 +39,6 @@
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginEnd="16dp"
tools:background="@color/black">

View File

@@ -4,7 +4,6 @@
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/conversation_item_thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="false"

View File

@@ -21,7 +21,7 @@
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/quote_author"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
@@ -30,6 +30,7 @@
android:maxLines="1"
android:textAppearance="@style/Signal.Text.LabelLarge"
android:textColor="@color/core_black"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/quote_attachment_name_stub"
app:layout_constraintEnd_toStartOf="@id/quote_missing_story_reaction_emoji"
app:layout_constraintHorizontal_bias="0"
@@ -37,7 +38,6 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_default="wrap"
tools:text="Peter Parker" />
<ViewStub
@@ -57,7 +57,7 @@
<TextView
android:id="@+id/media_type"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
@@ -70,7 +70,6 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/quote_bar"
app:layout_constraintTop_toBottomOf="@id/quote_attachment_name_stub"
app:layout_constraintWidth_default="wrap"
tools:text="Photo"
tools:visibility="visible" />
@@ -92,7 +91,6 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/quote_bar"
app:layout_constraintTop_toBottomOf="@id/media_type"
app:layout_constraintWidth_default="wrap"
tools:text="With great power comes great responsibility."
tools:visibility="visible" />