diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index 36c8994636..12d59d0868 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -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()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaBindingBridge.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaBindingBridge.kt index 684d98b0f4..5cd7a0420e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaBindingBridge.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaBindingBridge.kt @@ -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 + val thumbnailStub: Stub, + val quoteStub: Stub, + 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 ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt index cd81eb1d4a..9536dfbb9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt @@ -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>( 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>( thumbnailSize[0] = -1 thumbnailSize[1] = -1 - binding.textBridge.root.changeConstraints { - this.constrainMaxWidth(binding.textBridge.conversationItemBodyWrapper.id, 0) - } return } @@ -98,10 +171,6 @@ class V2ConversationItemMediaViewHolder>( 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>( ) } + 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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape.kt index b435145526..30da19d2d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape.kt @@ -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 } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt index 84c5143727..eb585792de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt @@ -100,6 +100,8 @@ open class V2ConversationItemTextOnlyViewHolder>( 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>( 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>( ) presentBody() - presentDate(shape) - presentDeliveryStatus(shape) - presentFooterBackground(shape) - presentFooterExpiry(shape) + presentDate() + presentDeliveryStatus() + presentFooterBackground() + presentFooterExpiry() presentAlert() presentSender() presentReactions() @@ -204,7 +206,7 @@ open class V2ConversationItemTextOnlyViewHolder>( Projection.relativeToParent( coordinateRoot, binding.conversationItemBodyWrapper, - Projection.Corners.NONE + shapeDelegate.corners ).translateX(binding.conversationItemBodyWrapper.translationX).translateY(root.translationY) ) @@ -397,7 +399,7 @@ open class V2ConversationItemTextOnlyViewHolder>( 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>( 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>( } } - 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>( ) } - 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>( } } - private fun presentDeliveryStatus(shape: V2ConversationItemShape.MessageShape) { + private fun presentDeliveryStatus() { val deliveryStatus = binding.conversationItemDeliveryStatus ?: return if (shape == V2ConversationItemShape.MessageShape.MIDDLE || shape == V2ConversationItemShape.MessageShape.START) { diff --git a/app/src/main/res/layout/v2_conversation_item_media_incoming.xml b/app/src/main/res/layout/v2_conversation_item_media_incoming.xml index 5bf19ca418..dda40c9ec1 100644 --- a/app/src/main/res/layout/v2_conversation_item_media_incoming.xml +++ b/app/src/main/res/layout/v2_conversation_item_media_incoming.xml @@ -55,7 +55,7 @@ + + + + + app:layout_constraintTop_toBottomOf="@id/conversation_item_quote_stub" /> diff --git a/app/src/main/res/layout/v2_conversation_item_media_outgoing.xml b/app/src/main/res/layout/v2_conversation_item_media_outgoing.xml index 43b409e08c..22b54af7cc 100644 --- a/app/src/main/res/layout/v2_conversation_item_media_outgoing.xml +++ b/app/src/main/res/layout/v2_conversation_item_media_outgoing.xml @@ -30,7 +30,7 @@ + + + + diff --git a/app/src/main/res/layout/v2_conversation_item_quote_stub.xml b/app/src/main/res/layout/v2_conversation_item_quote_stub.xml new file mode 100644 index 0000000000..e3a2099030 --- /dev/null +++ b/app/src/main/res/layout/v2_conversation_item_quote_stub.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml b/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml index 8dc25b6d02..5dccb7c8c5 100644 --- a/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml +++ b/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml @@ -55,7 +55,7 @@ diff --git a/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml b/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml index d04ed6a19e..681c26c4ce 100644 --- a/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml +++ b/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml @@ -30,7 +30,7 @@ diff --git a/app/src/main/res/layout/v2_conversation_item_thumbnail_stub.xml b/app/src/main/res/layout/v2_conversation_item_thumbnail_stub.xml index de4471e28a..0935efb6b7 100644 --- a/app/src/main/res/layout/v2_conversation_item_thumbnail_stub.xml +++ b/app/src/main/res/layout/v2_conversation_item_thumbnail_stub.xml @@ -4,7 +4,6 @@ --> @@ -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" />