Add the groundwork for the ConversationItemV2 Media item.
This commit is contained in:
parent
f9ab5d4013
commit
75b81a0fd2
16 changed files with 787 additions and 39 deletions
|
@ -623,6 +623,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||
viewModel.setUseConversationItemV2(!state.useConversationItemV2)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from("Use V2 ConversationItem for Media"),
|
||||
isChecked = state.useConversationItemV2ForMedia,
|
||||
onClick = {
|
||||
viewModel.setUseConversationItemV2Media(!state.useConversationItemV2ForMedia)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,5 +22,6 @@ data class InternalSettingsState(
|
|||
val disableStorageService: Boolean,
|
||||
val canClearOnboardingState: Boolean,
|
||||
val pnpInitialized: Boolean,
|
||||
val useConversationItemV2: Boolean
|
||||
val useConversationItemV2: Boolean,
|
||||
val useConversationItemV2ForMedia: Boolean
|
||||
)
|
||||
|
|
|
@ -109,6 +109,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||
refresh()
|
||||
}
|
||||
|
||||
fun setUseConversationItemV2Media(enabled: Boolean) {
|
||||
SignalStore.internalValues().setUseConversationItemV2Media(enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun addSampleReleaseNote() {
|
||||
repository.addSampleReleaseNote()
|
||||
}
|
||||
|
@ -136,7 +141,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
||||
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled(),
|
||||
pnpInitialized = SignalStore.misc().hasPniInitializedDevices(),
|
||||
useConversationItemV2 = SignalStore.internalValues().useConversationItemV2()
|
||||
useConversationItemV2 = SignalStore.internalValues().useConversationItemV2(),
|
||||
useConversationItemV2ForMedia = SignalStore.internalValues().useConversationItemV2Media()
|
||||
)
|
||||
|
||||
fun onClearOnboardingState() {
|
||||
|
|
|
@ -36,9 +36,12 @@ import org.thoughtcrime.securesms.conversation.v2.data.OutgoingMedia
|
|||
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ThreadHeader
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationContext
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2TextOnlyViewHolder
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemMediaViewHolder
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemTextOnlyViewHolder
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.bridge
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaIncomingBinding
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaOutgoingBinding
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemTextOnlyIncomingBinding
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemTextOnlyOutgoingBinding
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer
|
||||
|
@ -93,25 +96,37 @@ class ConversationAdapterV2(
|
|||
ConversationUpdateViewHolder(view)
|
||||
}
|
||||
|
||||
registerFactory(OutgoingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_sent_multimedia, parent, false)
|
||||
OutgoingMediaViewHolder(view)
|
||||
}
|
||||
if (SignalStore.internalValues().useConversationItemV2Media()) {
|
||||
registerFactory(OutgoingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_media_outgoing, parent, false)
|
||||
V2ConversationItemMediaViewHolder(V2ConversationItemMediaOutgoingBinding.bind(view).bridge(), this)
|
||||
}
|
||||
|
||||
registerFactory(IncomingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_received_multimedia, parent, false)
|
||||
IncomingMediaViewHolder(view)
|
||||
registerFactory(IncomingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_media_incoming, parent, false)
|
||||
V2ConversationItemMediaViewHolder(V2ConversationItemMediaIncomingBinding.bind(view).bridge(), this)
|
||||
}
|
||||
} else {
|
||||
registerFactory(OutgoingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_sent_multimedia, parent, false)
|
||||
OutgoingMediaViewHolder(view)
|
||||
}
|
||||
|
||||
registerFactory(IncomingMedia::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_received_multimedia, parent, false)
|
||||
IncomingMediaViewHolder(view)
|
||||
}
|
||||
}
|
||||
|
||||
if (SignalStore.internalValues().useConversationItemV2()) {
|
||||
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_text_only_outgoing, parent, false)
|
||||
V2TextOnlyViewHolder(V2ConversationItemTextOnlyOutgoingBinding.bind(view).bridge(), this)
|
||||
V2ConversationItemTextOnlyViewHolder(V2ConversationItemTextOnlyOutgoingBinding.bind(view).bridge(), this)
|
||||
}
|
||||
|
||||
registerFactory(IncomingTextOnly::class.java) { parent ->
|
||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_text_only_incoming, parent, false)
|
||||
V2TextOnlyViewHolder(V2ConversationItemTextOnlyIncomingBinding.bind(view).bridge(), this)
|
||||
V2ConversationItemTextOnlyViewHolder(V2ConversationItemTextOnlyIncomingBinding.bind(view).bridge(), this)
|
||||
}
|
||||
} else {
|
||||
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
|
|||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Path
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PointF
|
||||
|
@ -14,6 +15,7 @@ import android.graphics.Rect
|
|||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.toRectF
|
||||
import androidx.core.graphics.withClip
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.core.view.children
|
||||
|
@ -31,6 +33,7 @@ class ChatColorsDrawable : Drawable() {
|
|||
|
||||
companion object {
|
||||
private var maskDrawable: Drawable? = null
|
||||
private var latestBounds: Rect? = null
|
||||
|
||||
/**
|
||||
* Binds the ChatColorsDrawable static cache to the lifecycle of the given recycler-view
|
||||
|
@ -47,6 +50,7 @@ class ChatColorsDrawable : Drawable() {
|
|||
}
|
||||
|
||||
private fun applyBounds(bounds: Rect) {
|
||||
latestBounds = bounds
|
||||
maskDrawable?.bounds = bounds
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +69,7 @@ class ChatColorsDrawable : Drawable() {
|
|||
private val rect = RectF()
|
||||
|
||||
private var gradientColors: ChatColors? = null
|
||||
private var corners: FloatArray = floatArrayOf()
|
||||
private var corners: FloatArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
||||
private var fillColor: Int = 0
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
|
@ -98,6 +102,21 @@ class ChatColorsDrawable : Drawable() {
|
|||
return PixelFormat.TRANSLUCENT
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: APIs had the wrong name for setPath here, so we have to use the deprecated method.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getOutline(outline: Outline) {
|
||||
val path = Path()
|
||||
path.addRoundRect(
|
||||
bounds.toRectF(),
|
||||
corners,
|
||||
Path.Direction.CW
|
||||
)
|
||||
|
||||
outline.setConvexPath(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
@ -134,15 +153,20 @@ class ChatColorsDrawable : Drawable() {
|
|||
chatColors: ChatColors,
|
||||
corners: Corners
|
||||
) {
|
||||
this.gradientColors = chatColors
|
||||
this.corners = corners.toRadii()
|
||||
|
||||
if (chatColors.isGradient()) {
|
||||
if (maskDrawable == null) {
|
||||
maskDrawable = chatColors.chatBubbleMask
|
||||
|
||||
val maskBounds = latestBounds
|
||||
if (maskBounds != null) {
|
||||
maskDrawable?.bounds = maskBounds
|
||||
}
|
||||
}
|
||||
|
||||
this.fillColor = 0
|
||||
this.gradientColors = chatColors
|
||||
} else {
|
||||
this.fillColor = chatColors.asSingleColor()
|
||||
this.gradientColors = null
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.widget.ImageView
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaIncomingBinding
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaOutgoingBinding
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
|
||||
/**
|
||||
* Pass-through interface for bridging incoming and outgoing media message views.
|
||||
*
|
||||
* Essentially, just a convenience wrapper since the layouts differ *very slightly* and
|
||||
* we want to be able to have each follow the same code-path.
|
||||
*/
|
||||
data class V2ConversationItemMediaBindingBridge(
|
||||
val textBridge: V2ConversationItemTextOnlyBindingBridge,
|
||||
val thumbnailStub: Stub<ImageView>
|
||||
)
|
||||
|
||||
/**
|
||||
* Wraps the binding in the bridge.
|
||||
*/
|
||||
fun V2ConversationItemMediaIncomingBinding.bridge(): V2ConversationItemMediaBindingBridge {
|
||||
val textBridge = V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = groupMessageSender,
|
||||
senderPhoto = contactPhoto,
|
||||
senderBadge = badge,
|
||||
conversationItemBody = conversationItemBody,
|
||||
conversationItemBodyWrapper = conversationItemBodyWrapper,
|
||||
conversationItemReply = conversationItemReply,
|
||||
conversationItemReactions = conversationItemReactions,
|
||||
conversationItemDeliveryStatus = null,
|
||||
conversationItemFooterDate = conversationItemFooterDate,
|
||||
conversationItemFooterExpiry = conversationItemExpirationTimer,
|
||||
conversationItemFooterBackground = conversationItemFooterBackground,
|
||||
conversationItemAlert = null,
|
||||
conversationItemFooterSpace = null,
|
||||
isIncoming = true
|
||||
)
|
||||
|
||||
return V2ConversationItemMediaBindingBridge(
|
||||
textBridge = textBridge,
|
||||
thumbnailStub = Stub(conversationItemThumbnailStub)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the binding in the bridge.
|
||||
*/
|
||||
fun V2ConversationItemMediaOutgoingBinding.bridge(): V2ConversationItemMediaBindingBridge {
|
||||
val textBridge = V2ConversationItemTextOnlyBindingBridge(
|
||||
root = root,
|
||||
senderName = null,
|
||||
senderPhoto = null,
|
||||
senderBadge = null,
|
||||
conversationItemBody = conversationItemBody,
|
||||
conversationItemBodyWrapper = conversationItemBodyWrapper,
|
||||
conversationItemReply = conversationItemReply,
|
||||
conversationItemReactions = conversationItemReactions,
|
||||
conversationItemDeliveryStatus = conversationItemDeliveryStatus,
|
||||
conversationItemFooterDate = conversationItemFooterDate,
|
||||
conversationItemFooterExpiry = conversationItemExpirationTimer,
|
||||
conversationItemFooterBackground = conversationItemFooterBackground,
|
||||
conversationItemAlert = conversationItemAlert,
|
||||
conversationItemFooterSpace = footerEndPad,
|
||||
isIncoming = false
|
||||
)
|
||||
|
||||
return V2ConversationItemMediaBindingBridge(
|
||||
textBridge = textBridge,
|
||||
thumbnailStub = Stub(conversationItemThumbnailStub)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
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.conversation.v2.data.ConversationMessageElement
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.changeConstraints
|
||||
|
||||
/**
|
||||
* Represents a media-backed conversation item.
|
||||
*/
|
||||
class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
||||
private val binding: V2ConversationItemMediaBindingBridge,
|
||||
private val conversationContext: V2ConversationContext
|
||||
) : V2ConversationItemTextOnlyViewHolder<Model>(
|
||||
binding.textBridge,
|
||||
conversationContext,
|
||||
V2FooterPositionDelegate(binding)
|
||||
) {
|
||||
|
||||
private var thumbnailSlide: Slide? = null
|
||||
private var placeholderTarget: PlaceholderTarget? = null
|
||||
private val thumbnailSize = intArrayOf(0, 0)
|
||||
|
||||
init {
|
||||
binding.textBridge.conversationItemBodyWrapper.clipToOutline = true
|
||||
}
|
||||
|
||||
override fun bind(model: Model) {
|
||||
conversationMessage = (model as ConversationMessageElement).conversationMessage
|
||||
presentThumbnail()
|
||||
super.bind(model)
|
||||
}
|
||||
|
||||
private fun presentThumbnail() {
|
||||
val slideDeck = requireMediaMessage().slideDeck
|
||||
if (slideDeck.thumbnailSlides.isEmpty() || slideDeck.thumbnailSlides.size > 1) {
|
||||
binding.thumbnailStub.visibility = View.GONE
|
||||
thumbnailSize[0] = -1
|
||||
thumbnailSize[1] = -1
|
||||
|
||||
binding.textBridge.root.changeConstraints {
|
||||
this.constrainMaxWidth(binding.textBridge.conversationItemBodyWrapper.id, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
binding.thumbnailStub.visibility = View.VISIBLE
|
||||
|
||||
val thumbnail = slideDeck.thumbnailSlides.first()
|
||||
|
||||
// TODO [alex] -- Is this correct?
|
||||
if (thumbnail == thumbnailSlide) {
|
||||
return
|
||||
}
|
||||
|
||||
thumbnailSlide = thumbnail
|
||||
|
||||
conversationContext.glideRequests.clear(binding.thumbnailStub.get())
|
||||
|
||||
if (placeholderTarget != null) {
|
||||
conversationContext.glideRequests.clear(placeholderTarget)
|
||||
}
|
||||
// endif
|
||||
|
||||
val thumbnailUri = thumbnail.uri
|
||||
val thumbnailBlur = thumbnail.placeholderBlur
|
||||
|
||||
val thumbnailAttachment = thumbnail.asAttachment()
|
||||
val thumbnailWidth = thumbnailAttachment.width
|
||||
val thumbnailHeight = thumbnailAttachment.height
|
||||
|
||||
val maxWidth = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_width)
|
||||
val maxHeight = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_height)
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailWidth,
|
||||
thumbnailHeight,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
)
|
||||
|
||||
binding.thumbnailStub.get().updateLayoutParams {
|
||||
width = thumbnailSize[0]
|
||||
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
|
||||
.glideRequests
|
||||
.load(thumbnailBlur)
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize[0], thumbnailSize[1])
|
||||
.into(placeholderTarget)
|
||||
|
||||
this.placeholderTarget = placeholderTarget
|
||||
}
|
||||
|
||||
if (thumbnailUri != null) {
|
||||
conversationContext
|
||||
.glideRequests
|
||||
.load(DecryptableStreamUriLoader.DecryptableUri(thumbnailUri))
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize[0], thumbnailSize[1])
|
||||
.into(binding.thumbnailStub.get())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setThumbnailSize(
|
||||
thumbnailWidth: Int,
|
||||
thumbnailHeight: Int,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int
|
||||
) {
|
||||
if (thumbnailWidth == 0 || thumbnailHeight == 0) {
|
||||
thumbnailSize[0] = maxWidth
|
||||
thumbnailSize[1] = maxHeight
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth <= maxWidth && thumbnailHeight <= maxHeight) {
|
||||
thumbnailSize[0] = thumbnailWidth
|
||||
thumbnailSize[1] = thumbnailHeight
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth > maxWidth) {
|
||||
val thumbnailScale = 1 - ((thumbnailWidth - maxWidth) / thumbnailWidth.toFloat())
|
||||
|
||||
thumbnailSize[0] = (thumbnailWidth * thumbnailScale).toInt()
|
||||
thumbnailSize[1] = (thumbnailHeight * thumbnailScale).toInt()
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailHeight > maxHeight) {
|
||||
val thumbnailScale = 1 - ((thumbnailHeight - maxHeight) / thumbnailHeight.toFloat())
|
||||
|
||||
thumbnailSize[0] = (thumbnailWidth * thumbnailScale).toInt()
|
||||
thumbnailSize[1] = (thumbnailHeight * thumbnailScale).toInt()
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailSize[0],
|
||||
thumbnailSize[1],
|
||||
maxWidth,
|
||||
maxHeight
|
||||
)
|
||||
}
|
||||
|
||||
private fun isThumbnailMetricsSatisfied(maxWidth: Int, maxHeight: Int): Boolean {
|
||||
return thumbnailSize[0] in 1..maxWidth && thumbnailSize[1] in 1..maxHeight
|
||||
}
|
||||
|
||||
private fun requireMediaMessage(): MediaMmsMessageRecord {
|
||||
return conversationMessage.messageRecord as MediaMmsMessageRecord
|
||||
}
|
||||
|
||||
private inner class PlaceholderTarget(view: ImageView) : CustomViewTarget<ImageView, Drawable>(view) {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
view.background = errorDrawable
|
||||
}
|
||||
|
||||
override fun onResourceCleared(placeholder: Drawable?) {
|
||||
view.background = placeholder
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
view.background = resource
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.util.visible
|
|||
* Responsible for drawing the conversation bubble when a user long-presses it and the reaction
|
||||
* overlay appears.
|
||||
*/
|
||||
class V2TextOnlySnapshotStrategy(
|
||||
class V2ConversationItemSnapshotStrategy(
|
||||
private val binding: V2ConversationItemTextOnlyBindingBridge
|
||||
) : InteractiveConversationElement.SnapshotStrategy {
|
||||
|
|
@ -57,9 +57,10 @@ import java.util.Locale
|
|||
/**
|
||||
* Represents a text-only conversation item.
|
||||
*/
|
||||
class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
private val binding: V2ConversationItemTextOnlyBindingBridge,
|
||||
private val conversationContext: V2ConversationContext
|
||||
private val conversationContext: V2ConversationContext,
|
||||
footerDelegate: V2FooterPositionDelegate = V2FooterPositionDelegate(binding)
|
||||
) : V2ConversationItemViewHolder<Model>(binding.root, conversationContext), Multiselectable, InteractiveConversationElement {
|
||||
|
||||
companion object {
|
||||
|
@ -73,7 +74,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
private var messageId: Long = Long.MAX_VALUE
|
||||
|
||||
private val projections = ProjectionList()
|
||||
private val footerDelegate = V2FooterPositionDelegate(binding)
|
||||
private val dispatchTouchEventListener = V2OnDispatchTouchEventListener(conversationContext, binding)
|
||||
|
||||
override lateinit var conversationMessage: ConversationMessage
|
||||
|
@ -212,7 +212,7 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
}
|
||||
|
||||
override fun getSnapshotStrategy(): InteractiveConversationElement.SnapshotStrategy {
|
||||
return V2TextOnlySnapshotStrategy(binding)
|
||||
return V2ConversationItemSnapshotStrategy(binding)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,7 +324,10 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
binding.conversationItemBody.maxLines = Integer.MAX_VALUE
|
||||
}
|
||||
|
||||
binding.conversationItemBody.text = StringUtil.trim(styledText)
|
||||
val bodyText = StringUtil.trim(styledText)
|
||||
|
||||
binding.conversationItemBody.visible = bodyText.isNotEmpty()
|
||||
binding.conversationItemBody.text = bodyText
|
||||
}
|
||||
|
||||
private fun linkifyMessageBody(messageBody: Spannable) {
|
|
@ -6,11 +6,13 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.padding
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -18,15 +20,14 @@ import kotlin.math.max
|
|||
* Logical delegate for determining the footer position for a particular conversation item.
|
||||
*/
|
||||
class V2FooterPositionDelegate private constructor(
|
||||
private val isIncoming: Boolean,
|
||||
private val root: V2ConversationItemLayout,
|
||||
private val footerViews: List<View>,
|
||||
private val bodyContainer: View,
|
||||
private val body: EmojiTextView
|
||||
private val body: EmojiTextView,
|
||||
private val thumbnailView: Stub<ImageView>?
|
||||
) : V2ConversationItemLayout.OnMeasureListener {
|
||||
|
||||
constructor(binding: V2ConversationItemTextOnlyBindingBridge) : this(
|
||||
binding.isIncoming,
|
||||
binding.root,
|
||||
listOfNotNull(
|
||||
binding.conversationItemFooterDate,
|
||||
|
@ -35,7 +36,21 @@ class V2FooterPositionDelegate private constructor(
|
|||
binding.conversationItemFooterSpace
|
||||
),
|
||||
binding.conversationItemBodyWrapper,
|
||||
binding.conversationItemBody
|
||||
binding.conversationItemBody,
|
||||
null
|
||||
)
|
||||
|
||||
constructor(binding: V2ConversationItemMediaBindingBridge) : this(
|
||||
binding.textBridge.root,
|
||||
listOfNotNull(
|
||||
binding.textBridge.conversationItemFooterDate,
|
||||
binding.textBridge.conversationItemDeliveryStatus,
|
||||
binding.textBridge.conversationItemFooterExpiry,
|
||||
binding.textBridge.conversationItemFooterSpace
|
||||
),
|
||||
binding.textBridge.conversationItemBodyWrapper,
|
||||
binding.textBridge.conversationItemBody,
|
||||
binding.thumbnailStub
|
||||
)
|
||||
|
||||
private val gutters = 48.dp + 16.dp
|
||||
|
@ -48,7 +63,12 @@ class V2FooterPositionDelegate private constructor(
|
|||
}
|
||||
|
||||
override fun onPostMeasure(): Boolean {
|
||||
val maxWidth = root.measuredWidth - gutters
|
||||
val maxWidth = if (thumbnailView?.isVisible == true) {
|
||||
thumbnailView.get().layoutParams.width
|
||||
} else {
|
||||
root.measuredWidth - gutters
|
||||
}
|
||||
|
||||
val lastLineWidth = body.lastLineWidth
|
||||
val footerWidth = getFooterWidth()
|
||||
|
||||
|
@ -81,7 +101,7 @@ class V2FooterPositionDelegate private constructor(
|
|||
return
|
||||
}
|
||||
|
||||
bodyContainer.padding(right = 0, left = 0, bottom = footerViews.first().measuredHeight)
|
||||
body.padding(right = 0, left = 0, bottom = footerViews.first().measuredHeight)
|
||||
displayState = DisplayState.UNDERNEATH
|
||||
}
|
||||
|
||||
|
@ -90,7 +110,7 @@ class V2FooterPositionDelegate private constructor(
|
|||
return
|
||||
}
|
||||
|
||||
val targetWidth = body.measuredWidth + getFooterWidth()
|
||||
val targetWidth = body.measuredWidth + 24.dp + getFooterWidth()
|
||||
val end = max(0, targetWidth - bodyContainer.measuredWidth) - 8.dp
|
||||
val (left, right) = if (bodyContainer.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
0 to end
|
||||
|
@ -98,7 +118,7 @@ class V2FooterPositionDelegate private constructor(
|
|||
end to 0
|
||||
}
|
||||
|
||||
bodyContainer.padding(right = right, left = left, bottom = 0)
|
||||
body.padding(right = right, left = left, bottom = 0)
|
||||
displayState = DisplayState.END
|
||||
}
|
||||
|
||||
|
@ -107,7 +127,7 @@ class V2FooterPositionDelegate private constructor(
|
|||
return
|
||||
}
|
||||
|
||||
bodyContainer.padding(right = 0, left = 0, bottom = 0)
|
||||
body.padding(right = 0, left = 0, bottom = 0)
|
||||
displayState = DisplayState.TUCKED
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ public final class InternalValues extends SignalStoreValues {
|
|||
public static final String FORCE_WEBSOCKET_MODE = "internal.force_websocket_mode";
|
||||
public static final String LAST_SCROLL_POSITION = "internal.last_scroll_position";
|
||||
public static final String CONVERSATION_ITEM_V2 = "internal.conversation_item_v2";
|
||||
public static final String CONVERSATION_ITEM_V2_MEDIA = "internal.conversation_item_v2_media";
|
||||
|
||||
InternalValues(KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -198,4 +199,12 @@ public final class InternalValues extends SignalStoreValues {
|
|||
public boolean useConversationItemV2() {
|
||||
return FeatureFlags.internalUser() && getBoolean(CONVERSATION_ITEM_V2, false);
|
||||
}
|
||||
|
||||
public void setUseConversationItemV2Media(boolean useConversationFragmentV2Media) {
|
||||
putBoolean(CONVERSATION_ITEM_V2_MEDIA, useConversationFragmentV2Media);
|
||||
}
|
||||
|
||||
public boolean useConversationItemV2Media() {
|
||||
return FeatureFlags.internalUser() && getBoolean(CONVERSATION_ITEM_V2_MEDIA, false);
|
||||
}
|
||||
}
|
||||
|
|
192
app/src/main/res/layout/v2_conversation_item_media_incoming.xml
Normal file
192
app/src/main/res/layout/v2_conversation_item_media_incoming.xml
Normal file
|
@ -0,0 +1,192 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@+id/container"
|
||||
android:nextFocusRight="@+id/embedded_text_editor">
|
||||
|
||||
<!-- STR Icon -->
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/conversation_item_reply"
|
||||
android:layout_width="@dimen/conversation_item_reply_size"
|
||||
android:layout_height="@dimen/conversation_item_reply_size"
|
||||
android:alpha="0"
|
||||
android:tint="@color/signal_icon_tint_secondary"
|
||||
app:contentPadding="9dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintTop_toTopOf="@id/conversation_item_body_wrapper"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle"
|
||||
app:srcCompat="@drawable/symbol_reply_24" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/contact_photo"
|
||||
android:layout_width="@dimen/conversation_item_avatar_size"
|
||||
android:layout_height="@dimen/conversation_item_avatar_size"
|
||||
android:layout_marginStart="12dp"
|
||||
android:contentDescription="@string/conversation_item_received__contact_photo_description"
|
||||
android:cropToPadding="true"
|
||||
android:visibility="gone"
|
||||
app:fallbackImageSize="small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
app:badge_size="small"
|
||||
app:layout_constraintStart_toStartOf="@id/contact_photo"
|
||||
app:layout_constraintTop_toTopOf="@id/contact_photo"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Body -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/conversation_item_body_wrapper"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:orientation="vertical"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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_height="wrap_content"
|
||||
android:layout_marginTop="7dp"
|
||||
android:layout_marginEnd="4sp"
|
||||
android:layout_marginBottom="-6dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/message_bubble_horizontal_padding"
|
||||
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_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" />
|
||||
|
||||
<!-- 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: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" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
||||
android:textSize="16sp"
|
||||
app:emoji_maxLength="1000"
|
||||
app:emoji_renderMentions="true"
|
||||
app:emoji_renderSpoilers="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Footer -->
|
||||
<View
|
||||
android:id="@+id/conversation_item_footer_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="-12dp"
|
||||
android:layout_marginEnd="-12dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_footer_date"
|
||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_expiration_timer"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_item_footer_date"
|
||||
app:layout_constraintTop_toTopOf="@id/conversation_item_footer_date"
|
||||
tools:background="@color/blue_500"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/conversation_item_footer_date"
|
||||
style="@style/Signal.Text.Caption.MessageSent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:autoLink="none"
|
||||
android:ellipsize="end"
|
||||
android:linksClickable="false"
|
||||
android:maxLines="1"
|
||||
android:paddingBottom="@dimen/message_bubble_bottom_padding"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintEnd_toStartOf="@id/conversation_item_expiration_timer"
|
||||
app:layout_goneMarginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
tools:text="13:14pm" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.ExpirationTimerView
|
||||
android:id="@+id/conversation_item_expiration_timer"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_footer_date"
|
||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintTop_toTopOf="@id/conversation_item_footer_date" />
|
||||
<!-- End Footer -->
|
||||
|
||||
<!-- Replies Icon -->
|
||||
<!-- Reactions -->
|
||||
<org.thoughtcrime.securesms.reactions.ReactionsConversationView
|
||||
android:id="@+id/conversation_item_reactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:rcv_outgoing="false" />
|
||||
|
||||
</org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemLayout>
|
179
app/src/main/res/layout/v2_conversation_item_media_outgoing.xml
Normal file
179
app/src/main/res/layout/v2_conversation_item_media_outgoing.xml
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@+id/container"
|
||||
android:nextFocusRight="@+id/embedded_text_editor">
|
||||
|
||||
<!-- STR Icon -->
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/conversation_item_reply"
|
||||
android:layout_width="@dimen/conversation_item_reply_size"
|
||||
android:layout_height="@dimen/conversation_item_reply_size"
|
||||
android:alpha="0"
|
||||
android:tint="@color/signal_icon_tint_secondary"
|
||||
app:contentPadding="9dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintTop_toTopOf="@id/conversation_item_body_wrapper"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle"
|
||||
app:srcCompat="@drawable/symbol_reply_24" />
|
||||
|
||||
<!-- Body -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/conversation_item_body_wrapper"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/conversation_item_alert"
|
||||
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">
|
||||
|
||||
<!-- Media content goes here -->
|
||||
<ViewStub
|
||||
android:id="@+id/conversation_item_thumbnail_stub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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_goneMarginTop="0dp" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
||||
android:textSize="16sp"
|
||||
app:emoji_maxLength="1000"
|
||||
app:emoji_renderMentions="true"
|
||||
app:emoji_renderSpoilers="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.AlertView
|
||||
android:id="@+id/conversation_item_alert"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- Footer -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_footer_top"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="conversation_item_delivery_status,conversation_item_footer_date" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_footer_bottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="conversation_item_delivery_status,conversation_item_footer_date" />
|
||||
|
||||
<View
|
||||
android:id="@+id/conversation_item_footer_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="-12dp"
|
||||
android:layout_marginEnd="-12dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@color/blue_500"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/barrier_footer_bottom"
|
||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_delivery_status"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_item_footer_date"
|
||||
app:layout_constraintTop_toTopOf="@id/barrier_footer_top"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/conversation_item_footer_date"
|
||||
style="@style/Signal.Text.Caption.MessageSent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:autoLink="none"
|
||||
android:ellipsize="end"
|
||||
android:linksClickable="false"
|
||||
android:maxLines="1"
|
||||
android:paddingBottom="@dimen/message_bubble_bottom_padding"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintEnd_toStartOf="@id/conversation_item_expiration_timer"
|
||||
tools:text="13:14pm" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.ExpirationTimerView
|
||||
android:id="@+id/conversation_item_expiration_timer"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintEnd_toStartOf="@id/conversation_item_delivery_status" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.DeliveryStatusView
|
||||
android:id="@+id/conversation_item_delivery_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingBottom="@dimen/message_bubble_bottom_padding"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintEnd_toStartOf="@id/footer_end_pad" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/footer_end_pad"
|
||||
android:layout_width="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_height="@dimen/message_bubble_horizontal_padding"
|
||||
app:layout_constraintBottom_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper" />
|
||||
|
||||
<!-- End Footer -->
|
||||
|
||||
<!-- Replies Icon -->
|
||||
<!-- Reactions -->
|
||||
<org.thoughtcrime.securesms.reactions.ReactionsConversationView
|
||||
android:id="@+id/conversation_item_reactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_item_body_wrapper"
|
||||
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
||||
app:rcv_outgoing="true" />
|
||||
|
||||
</org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemLayout>
|
|
@ -55,7 +55,7 @@
|
|||
<!-- Body -->
|
||||
<LinearLayout
|
||||
android:id="@+id/conversation_item_body_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
|
@ -66,6 +66,7 @@
|
|||
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">
|
||||
|
||||
|
@ -90,10 +91,10 @@
|
|||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingTop="@dimen/message_bubble_top_padding"
|
||||
android:paddingEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<!-- Body -->
|
||||
<FrameLayout
|
||||
android:id="@+id/conversation_item_body_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
app:cardElevation="0dp"
|
||||
|
@ -39,6 +39,7 @@
|
|||
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">
|
||||
|
||||
|
@ -46,10 +47,10 @@
|
|||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingTop="@dimen/message_bubble_top_padding"
|
||||
android:paddingEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:paddingBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<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"
|
||||
android:contentDescription="@string/conversation_activity__attachment_thumbnail"
|
||||
android:scaleType="centerInside"
|
||||
tools:viewBindingIgnore="true" />
|
Loading…
Add table
Reference in a new issue