mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-28 04:34:21 +01:00
Add the groundwork for the ConversationItemV2 Media item.
This commit is contained in:
committed by
Greyson Parrelli
parent
f9ab5d4013
commit
75b81a0fd2
@@ -623,6 +623,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||||||
viewModel.setUseConversationItemV2(!state.useConversationItemV2)
|
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 disableStorageService: Boolean,
|
||||||
val canClearOnboardingState: Boolean,
|
val canClearOnboardingState: Boolean,
|
||||||
val pnpInitialized: Boolean,
|
val pnpInitialized: Boolean,
|
||||||
val useConversationItemV2: Boolean
|
val useConversationItemV2: Boolean,
|
||||||
|
val useConversationItemV2ForMedia: Boolean
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -109,6 +109,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setUseConversationItemV2Media(enabled: Boolean) {
|
||||||
|
SignalStore.internalValues().setUseConversationItemV2Media(enabled)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun addSampleReleaseNote() {
|
fun addSampleReleaseNote() {
|
||||||
repository.addSampleReleaseNote()
|
repository.addSampleReleaseNote()
|
||||||
}
|
}
|
||||||
@@ -136,7 +141,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
|||||||
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
||||||
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled(),
|
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled(),
|
||||||
pnpInitialized = SignalStore.misc().hasPniInitializedDevices(),
|
pnpInitialized = SignalStore.misc().hasPniInitializedDevices(),
|
||||||
useConversationItemV2 = SignalStore.internalValues().useConversationItemV2()
|
useConversationItemV2 = SignalStore.internalValues().useConversationItemV2(),
|
||||||
|
useConversationItemV2ForMedia = SignalStore.internalValues().useConversationItemV2Media()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun onClearOnboardingState() {
|
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.OutgoingTextOnly
|
||||||
import org.thoughtcrime.securesms.conversation.v2.data.ThreadHeader
|
import org.thoughtcrime.securesms.conversation.v2.data.ThreadHeader
|
||||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationContext
|
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.conversation.v2.items.bridge
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
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.V2ConversationItemTextOnlyIncomingBinding
|
||||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemTextOnlyOutgoingBinding
|
import org.thoughtcrime.securesms.databinding.V2ConversationItemTextOnlyOutgoingBinding
|
||||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer
|
||||||
@@ -93,25 +96,37 @@ class ConversationAdapterV2(
|
|||||||
ConversationUpdateViewHolder(view)
|
ConversationUpdateViewHolder(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerFactory(OutgoingMedia::class.java) { parent ->
|
if (SignalStore.internalValues().useConversationItemV2Media()) {
|
||||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_sent_multimedia, parent, false)
|
registerFactory(OutgoingMedia::class.java) { parent ->
|
||||||
OutgoingMediaViewHolder(view)
|
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 ->
|
registerFactory(IncomingMedia::class.java) { parent ->
|
||||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.conversation_item_received_multimedia, parent, false)
|
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_media_incoming, parent, false)
|
||||||
IncomingMediaViewHolder(view)
|
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()) {
|
if (SignalStore.internalValues().useConversationItemV2()) {
|
||||||
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
||||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_text_only_outgoing, parent, false)
|
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 ->
|
registerFactory(IncomingTextOnly::class.java) { parent ->
|
||||||
val view = CachedInflater.from(parent.context).inflate<View>(R.layout.v2_conversation_item_text_only_incoming, parent, false)
|
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 {
|
} else {
|
||||||
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
registerFactory(OutgoingTextOnly::class.java) { parent ->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
|
|||||||
|
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.ColorFilter
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Outline
|
||||||
import android.graphics.Path
|
import android.graphics.Path
|
||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
@@ -14,6 +15,7 @@ import android.graphics.Rect
|
|||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.graphics.toRectF
|
||||||
import androidx.core.graphics.withClip
|
import androidx.core.graphics.withClip
|
||||||
import androidx.core.graphics.withTranslation
|
import androidx.core.graphics.withTranslation
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
@@ -31,6 +33,7 @@ class ChatColorsDrawable : Drawable() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var maskDrawable: Drawable? = null
|
private var maskDrawable: Drawable? = null
|
||||||
|
private var latestBounds: Rect? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds the ChatColorsDrawable static cache to the lifecycle of the given recycler-view
|
* 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) {
|
private fun applyBounds(bounds: Rect) {
|
||||||
|
latestBounds = bounds
|
||||||
maskDrawable?.bounds = bounds
|
maskDrawable?.bounds = bounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +69,7 @@ class ChatColorsDrawable : Drawable() {
|
|||||||
private val rect = RectF()
|
private val rect = RectF()
|
||||||
|
|
||||||
private var gradientColors: ChatColors? = null
|
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
|
private var fillColor: Int = 0
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
@@ -98,6 +102,21 @@ class ChatColorsDrawable : Drawable() {
|
|||||||
return PixelFormat.TRANSLUCENT
|
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.
|
* 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,
|
* Also applies the given [Projection]'s (x,y) (Top, Left) coordinates as the mask offset,
|
||||||
@@ -134,15 +153,20 @@ class ChatColorsDrawable : Drawable() {
|
|||||||
chatColors: ChatColors,
|
chatColors: ChatColors,
|
||||||
corners: Corners
|
corners: Corners
|
||||||
) {
|
) {
|
||||||
this.gradientColors = chatColors
|
|
||||||
this.corners = corners.toRadii()
|
this.corners = corners.toRadii()
|
||||||
|
|
||||||
if (chatColors.isGradient()) {
|
if (chatColors.isGradient()) {
|
||||||
if (maskDrawable == null) {
|
if (maskDrawable == null) {
|
||||||
maskDrawable = chatColors.chatBubbleMask
|
maskDrawable = chatColors.chatBubbleMask
|
||||||
|
|
||||||
|
val maskBounds = latestBounds
|
||||||
|
if (maskBounds != null) {
|
||||||
|
maskDrawable?.bounds = maskBounds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fillColor = 0
|
this.fillColor = 0
|
||||||
|
this.gradientColors = chatColors
|
||||||
} else {
|
} else {
|
||||||
this.fillColor = chatColors.asSingleColor()
|
this.fillColor = chatColors.asSingleColor()
|
||||||
this.gradientColors = null
|
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
|
* Responsible for drawing the conversation bubble when a user long-presses it and the reaction
|
||||||
* overlay appears.
|
* overlay appears.
|
||||||
*/
|
*/
|
||||||
class V2TextOnlySnapshotStrategy(
|
class V2ConversationItemSnapshotStrategy(
|
||||||
private val binding: V2ConversationItemTextOnlyBindingBridge
|
private val binding: V2ConversationItemTextOnlyBindingBridge
|
||||||
) : InteractiveConversationElement.SnapshotStrategy {
|
) : InteractiveConversationElement.SnapshotStrategy {
|
||||||
|
|
||||||
@@ -57,9 +57,10 @@ import java.util.Locale
|
|||||||
/**
|
/**
|
||||||
* Represents a text-only conversation item.
|
* Represents a text-only conversation item.
|
||||||
*/
|
*/
|
||||||
class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||||
private val binding: V2ConversationItemTextOnlyBindingBridge,
|
private val binding: V2ConversationItemTextOnlyBindingBridge,
|
||||||
private val conversationContext: V2ConversationContext
|
private val conversationContext: V2ConversationContext,
|
||||||
|
footerDelegate: V2FooterPositionDelegate = V2FooterPositionDelegate(binding)
|
||||||
) : V2ConversationItemViewHolder<Model>(binding.root, conversationContext), Multiselectable, InteractiveConversationElement {
|
) : V2ConversationItemViewHolder<Model>(binding.root, conversationContext), Multiselectable, InteractiveConversationElement {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -73,7 +74,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||||||
private var messageId: Long = Long.MAX_VALUE
|
private var messageId: Long = Long.MAX_VALUE
|
||||||
|
|
||||||
private val projections = ProjectionList()
|
private val projections = ProjectionList()
|
||||||
private val footerDelegate = V2FooterPositionDelegate(binding)
|
|
||||||
private val dispatchTouchEventListener = V2OnDispatchTouchEventListener(conversationContext, binding)
|
private val dispatchTouchEventListener = V2OnDispatchTouchEventListener(conversationContext, binding)
|
||||||
|
|
||||||
override lateinit var conversationMessage: ConversationMessage
|
override lateinit var conversationMessage: ConversationMessage
|
||||||
@@ -212,7 +212,7 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getSnapshotStrategy(): InteractiveConversationElement.SnapshotStrategy {
|
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.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) {
|
private fun linkifyMessageBody(messageBody: Spannable) {
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.items
|
package org.thoughtcrime.securesms.conversation.v2.items
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
import org.signal.core.util.dp
|
import org.signal.core.util.dp
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.padding
|
import org.thoughtcrime.securesms.util.padding
|
||||||
|
import org.thoughtcrime.securesms.util.views.Stub
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@@ -18,15 +20,14 @@ import kotlin.math.max
|
|||||||
* Logical delegate for determining the footer position for a particular conversation item.
|
* Logical delegate for determining the footer position for a particular conversation item.
|
||||||
*/
|
*/
|
||||||
class V2FooterPositionDelegate private constructor(
|
class V2FooterPositionDelegate private constructor(
|
||||||
private val isIncoming: Boolean,
|
|
||||||
private val root: V2ConversationItemLayout,
|
private val root: V2ConversationItemLayout,
|
||||||
private val footerViews: List<View>,
|
private val footerViews: List<View>,
|
||||||
private val bodyContainer: View,
|
private val bodyContainer: View,
|
||||||
private val body: EmojiTextView
|
private val body: EmojiTextView,
|
||||||
|
private val thumbnailView: Stub<ImageView>?
|
||||||
) : V2ConversationItemLayout.OnMeasureListener {
|
) : V2ConversationItemLayout.OnMeasureListener {
|
||||||
|
|
||||||
constructor(binding: V2ConversationItemTextOnlyBindingBridge) : this(
|
constructor(binding: V2ConversationItemTextOnlyBindingBridge) : this(
|
||||||
binding.isIncoming,
|
|
||||||
binding.root,
|
binding.root,
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
binding.conversationItemFooterDate,
|
binding.conversationItemFooterDate,
|
||||||
@@ -35,7 +36,21 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
binding.conversationItemFooterSpace
|
binding.conversationItemFooterSpace
|
||||||
),
|
),
|
||||||
binding.conversationItemBodyWrapper,
|
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
|
private val gutters = 48.dp + 16.dp
|
||||||
@@ -48,7 +63,12 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostMeasure(): Boolean {
|
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 lastLineWidth = body.lastLineWidth
|
||||||
val footerWidth = getFooterWidth()
|
val footerWidth = getFooterWidth()
|
||||||
|
|
||||||
@@ -81,7 +101,7 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyContainer.padding(right = 0, left = 0, bottom = footerViews.first().measuredHeight)
|
body.padding(right = 0, left = 0, bottom = footerViews.first().measuredHeight)
|
||||||
displayState = DisplayState.UNDERNEATH
|
displayState = DisplayState.UNDERNEATH
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +110,7 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetWidth = body.measuredWidth + getFooterWidth()
|
val targetWidth = body.measuredWidth + 24.dp + getFooterWidth()
|
||||||
val end = max(0, targetWidth - bodyContainer.measuredWidth) - 8.dp
|
val end = max(0, targetWidth - bodyContainer.measuredWidth) - 8.dp
|
||||||
val (left, right) = if (bodyContainer.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
val (left, right) = if (bodyContainer.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||||
0 to end
|
0 to end
|
||||||
@@ -98,7 +118,7 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
end to 0
|
end to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyContainer.padding(right = right, left = left, bottom = 0)
|
body.padding(right = right, left = left, bottom = 0)
|
||||||
displayState = DisplayState.END
|
displayState = DisplayState.END
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +127,7 @@ class V2FooterPositionDelegate private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyContainer.padding(right = 0, left = 0, bottom = 0)
|
body.padding(right = 0, left = 0, bottom = 0)
|
||||||
displayState = DisplayState.TUCKED
|
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 FORCE_WEBSOCKET_MODE = "internal.force_websocket_mode";
|
||||||
public static final String LAST_SCROLL_POSITION = "internal.last_scroll_position";
|
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 = "internal.conversation_item_v2";
|
||||||
|
public static final String CONVERSATION_ITEM_V2_MEDIA = "internal.conversation_item_v2_media";
|
||||||
|
|
||||||
InternalValues(KeyValueStore store) {
|
InternalValues(KeyValueStore store) {
|
||||||
super(store);
|
super(store);
|
||||||
@@ -198,4 +199,12 @@ public final class InternalValues extends SignalStoreValues {
|
|||||||
public boolean useConversationItemV2() {
|
public boolean useConversationItemV2() {
|
||||||
return FeatureFlags.internalUser() && getBoolean(CONVERSATION_ITEM_V2, false);
|
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 -->
|
<!-- Body -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/conversation_item_body_wrapper"
|
android:id="@+id/conversation_item_body_wrapper"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="48dp"
|
android:layout_marginEnd="48dp"
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/contact_photo"
|
app:layout_constraintStart_toEndOf="@id/contact_photo"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_default="wrap"
|
||||||
app:layout_goneMarginStart="16dp"
|
app:layout_goneMarginStart="16dp"
|
||||||
tools:background="@color/black">
|
tools:background="@color/black">
|
||||||
|
|
||||||
@@ -90,10 +91,10 @@
|
|||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
android:paddingTop="@dimen/message_bubble_top_padding"
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
android:paddingEnd="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||||
android:paddingBottom="@dimen/message_bubble_collapsed_footer_padding"
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||||
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
android:textColorLink="@color/conversation_item_sent_text_primary_color"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/conversation_item_body_wrapper"
|
android:id="@+id/conversation_item_body_wrapper"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="48dp"
|
android:layout_marginStart="48dp"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_default="wrap"
|
||||||
app:layout_goneMarginEnd="16dp"
|
app:layout_goneMarginEnd="16dp"
|
||||||
tools:background="@color/black">
|
tools:background="@color/black">
|
||||||
|
|
||||||
@@ -46,10 +47,10 @@
|
|||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
android:paddingTop="@dimen/message_bubble_top_padding"
|
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||||
android:paddingEnd="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||||
android:paddingBottom="@dimen/message_bubble_collapsed_footer_padding"
|
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||||
android:textColor="@color/conversation_item_sent_text_primary_color"
|
android:textColor="@color/conversation_item_sent_text_primary_color"
|
||||||
android:textColorLink="@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" />
|
||||||
Reference in New Issue
Block a user