Bubbles: handle images and make small refactoring

This commit is contained in:
ganfra 2022-01-18 19:27:12 +01:00
parent a9e7c45074
commit 5ee4984ec8
12 changed files with 131 additions and 113 deletions

View file

@ -54,6 +54,7 @@
<dimen name="chat_bubble_margin_start">28dp</dimen> <dimen name="chat_bubble_margin_start">28dp</dimen>
<dimen name="chat_bubble_margin_end">62dp</dimen> <dimen name="chat_bubble_margin_end">62dp</dimen>
<dimen name="chat_bubble_fixed_size">300dp</dimen> <dimen name="chat_bubble_fixed_size">300dp</dimen>
<dimen name="chat_bubble_corner_radius">12dp</dimen>
<!-- Onboarding --> <!-- Onboarding -->
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item> <item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>

View file

@ -4,12 +4,6 @@
<style name="TimelineContentStubBaseParams"> <style name="TimelineContentStubBaseParams">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginLeft">8dp</item>
<item name="android:layout_marginEnd">8dp</item>
<item name="android:layout_marginRight">8dp</item>
<item name="android:layout_marginBottom">4dp</item>
<item name="android:layout_marginTop">4dp</item>
</style> </style>
<style name="TimelineContentMediaPillStyle"> <style name="TimelineContentMediaPillStyle">

View file

@ -29,7 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
import im.vector.app.features.reactions.widget.ReactionButton import im.vector.app.features.reactions.widget.ReactionButton
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
@ -99,10 +99,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
holder.view.onClick(baseAttributes.itemClickListener) holder.view.onClick(baseAttributes.itemClickListener)
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener) holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
(holder.view as? MessageViewConfiguration)?.apply { (holder.view as? TimelineMessageLayoutRenderer)?.render(baseAttributes.informationData.messageLayout)
isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender
isLastFromSender = baseAttributes.informationData.isLastFromThisSender
}
} }
override fun unbind(holder: H) { override fun unbind(holder: H) {

View file

@ -23,14 +23,17 @@ import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.onClick
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@ -56,7 +59,21 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
imageContentRenderer.render(mediaData, mode, holder.imageView) val messageLayout = baseAttributes.informationData.messageLayout
val dimensionConverter = DimensionConverter(holder.view.resources)
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
val cornerRadius = holder.view.resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
val topRadius = if (messageLayout.isFirstFromThisSender) cornerRadius else 0f
val bottomRadius = if (messageLayout.isLastFromThisSender) cornerRadius else 0f
if (messageLayout.isIncoming) {
GranularRoundedCorners(topRadius, cornerRadius, cornerRadius, bottomRadius)
} else {
GranularRoundedCorners(cornerRadius, topRadius, bottomRadius, cornerRadius)
}
} else {
RoundedCorners(dimensionConverter.dpToPx(8))
}
imageContentRenderer.render(mediaData, mode, holder.imageView, imageCornerTransformation)
if (!attributes.informationData.sendState.hasFailed()) { if (!attributes.informationData.sendState.hasFailed()) {
contentUploadStateTrackerBinder.bind( contentUploadStateTrackerBinder.bind(
attributes.informationData.eventId, attributes.informationData.eventId,
@ -72,8 +89,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
holder.mediaContentView.onClick(attributes.itemClickListener) holder.mediaContentView.onClick(attributes.itemClickListener)
holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener) holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
(holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = true holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble
holder.overlayView.isVisible = baseAttributes.informationData.messageLayout is TimelineMessageLayout.Bubble
} }
override fun unbind(holder: Holder) { override fun unbind(holder: Holder) {

View file

@ -41,10 +41,11 @@ sealed interface TimelineMessageLayout : Parcelable {
val isIncoming: Boolean, val isIncoming: Boolean,
val isFirstFromThisSender: Boolean, val isFirstFromThisSender: Boolean,
val isLastFromThisSender: Boolean, val isLastFromThisSender: Boolean,
val isPseudoBubble: Boolean,
override val layoutRes: Int = if (isIncoming) { override val layoutRes: Int = if (isIncoming) {
R.layout.item_timeline_event_bubble_incoming_base R.layout.item_timeline_event_bubble_incoming_base
} else { } else {
R.layout.item_timeline_event_bubble_outgoing_base R.layout.item_timeline_event_bubble_outgoing_base
}, }
) : TimelineMessageLayout ) : TimelineMessageLayout
} }

View file

@ -33,15 +33,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
private val vectorPreferences: VectorPreferences) { private val vectorPreferences: VectorPreferences) {
companion object { companion object {
// Can't be rendered in bubbles, so get back to default layout
private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf( private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
EventType.MESSAGE, EventType.MESSAGE,
EventType.POLL_START, EventType.POLL_START,
EventType.ENCRYPTED, EventType.ENCRYPTED,
EventType.STICKER EventType.STICKER
) )
// Can't be rendered in bubbles, so get back to default layout
private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf( private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
MessageType.MSGTYPE_VERIFICATION_REQUEST MessageType.MSGTYPE_VERIFICATION_REQUEST
) )
// Use the bubble layout but without borders
private val MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT = setOf(
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO,
)
} }
fun create(params: TimelineItemFactoryParams): TimelineMessageLayout { fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
@ -86,6 +93,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
isIncoming = !isSentByMe, isIncoming = !isSentByMe,
isFirstFromThisSender = isFirstFromThisSender, isFirstFromThisSender = isFirstFromThisSender,
isLastFromThisSender = isLastFromThisSender, isLastFromThisSender = isLastFromThisSender,
isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
) )
} }
} else { } else {

View file

@ -18,13 +18,12 @@ package im.vector.app.features.home.room.detail.timeline.view
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
@ -35,70 +34,38 @@ import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.shape.ShapeAppearanceModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewMessageBubbleBinding
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber
class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : defStyleAttr: Int = 0) :
RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration { RelativeLayout(context, attrs, defStyleAttr), TimelineMessageLayoutRenderer {
override var isIncoming: Boolean = false private var isIncoming: Boolean = false
set(value) {
field = value
render()
}
override var isFirstFromSender: Boolean = false private val cornerRadius = resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
set(value) { private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12)
field = value private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
render()
}
override var isLastFromSender: Boolean = false
set(value) {
field = value
render()
}
override var showTimeAsOverlay: Boolean = false private lateinit var views: ViewMessageBubbleBinding
set(value) {
field = value
render()
}
private val cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
init { init {
inflate(context, R.layout.view_message_bubble, this) inflate(context, R.layout.view_message_bubble, this)
context.withStyledAttributes(attrs, R.styleable.MessageBubble) { context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false) isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, false)
isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
} }
} }
override fun onFinishInflate() { override fun onFinishInflate() {
super.onFinishInflate() super.onFinishInflate()
render() views = ViewMessageBubbleBinding.bind(this)
}
private fun render() {
val currentLayoutDirection = layoutDirection val currentLayoutDirection = layoutDirection
val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView)
bubbleView.apply {
background = createBackgroundDrawable()
outlineProvider = ViewOutlineProvider.BACKGROUND
clipToOutline = true
}
if (isIncoming) { if (isIncoming) {
findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection views.informationBottom.layoutDirection = currentLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection views.bubbleWrapper.layoutDirection = currentLayoutDirection
bubbleView.layoutDirection = currentLayoutDirection views.bubbleView.layoutDirection = currentLayoutDirection
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
}
findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
}
} else { } else {
val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) { val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
View.LAYOUT_DIRECTION_RTL View.LAYOUT_DIRECTION_RTL
@ -106,41 +73,66 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
View.LAYOUT_DIRECTION_LTR View.LAYOUT_DIRECTION_LTR
} }
findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection views.informationBottom.layoutDirection = oppositeLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection views.bubbleWrapper.layoutDirection = oppositeLayoutDirection
bubbleView.layoutDirection = currentLayoutDirection views.bubbleView.layoutDirection = currentLayoutDirection
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
}
findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
}
}
ConstraintSet().apply {
clone(bubbleView)
clear(R.id.viewStubContainer, ConstraintSet.END)
if (showTimeAsOverlay) {
val timeColor = ContextCompat.getColor(context, R.color.palette_white)
findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
val margin = resources.getDimensionPixelSize(R.dimen.layout_horizontal_margin)
setMargin(R.id.messageTimeView, ConstraintSet.END, margin)
} else {
val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
}
applyTo(bubbleView)
} }
} }
private fun createBackgroundDrawable(): Drawable { override fun render(messageLayout: TimelineMessageLayout) {
val (topCornerFamily, topRadius) = if (isFirstFromSender) { if (messageLayout !is TimelineMessageLayout.Bubble) {
Timber.v("Can't render messageLayout $messageLayout")
return
}
views.bubbleView.apply {
background = createBackgroundDrawable(messageLayout)
outlineProvider = ViewOutlineProvider.BACKGROUND
clipToOutline = true
}
ConstraintSet().apply {
clone(views.bubbleView)
clear(R.id.viewStubContainer, ConstraintSet.END)
val showTimeAsOverlay = messageLayout.isPseudoBubble
if (showTimeAsOverlay) {
val timeColor = ContextCompat.getColor(context, R.color.palette_white)
views.messageTimeView.setTextColor(timeColor)
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
} else {
val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
views.messageTimeView.setTextColor(timeColor)
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
}
applyTo(views.bubbleView)
}
if (messageLayout.isPseudoBubble) {
views.viewStubContainer.root.setPadding(0, 0, 0, 0)
} else {
views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)
}
if (messageLayout.isIncoming) {
views.messageEndGuideline.updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
}
views.messageStartGuideline.updateLayoutParams<LayoutParams> {
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
}
} else {
views.messageEndGuideline.updateLayoutParams<LayoutParams> {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
}
views.messageStartGuideline.updateLayoutParams<LayoutParams> {
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
}
}
}
private fun createBackgroundDrawable(messageLayout: TimelineMessageLayout.Bubble): Drawable {
val (topCornerFamily, topRadius) = if (messageLayout.isFirstFromThisSender) {
Pair(CornerFamily.ROUNDED, cornerRadius) Pair(CornerFamily.ROUNDED, cornerRadius)
} else { } else {
Pair(CornerFamily.CUT, 0f) Pair(CornerFamily.CUT, 0f)
} }
val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) { val (bottomCornerFamily, bottomRadius) = if (messageLayout.isLastFromThisSender) {
Pair(CornerFamily.ROUNDED, cornerRadius) Pair(CornerFamily.ROUNDED, cornerRadius)
} else { } else {
Pair(CornerFamily.CUT, 0f) Pair(CornerFamily.CUT, 0f)
@ -166,7 +158,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
} }
val shapeAppearanceModel = shapeAppearanceModelBuilder.build() val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
return MaterialShapeDrawable(shapeAppearanceModel).apply { return MaterialShapeDrawable(shapeAppearanceModel).apply {
fillColor = ColorStateList.valueOf(backgroundColor) fillColor = if (messageLayout.isPseudoBubble) {
ColorStateList.valueOf(Color.TRANSPARENT)
} else {
ColorStateList.valueOf(backgroundColor)
}
} }
} }
} }

View file

@ -16,11 +16,8 @@
package im.vector.app.features.home.room.detail.timeline.view package im.vector.app.features.home.room.detail.timeline.view
interface MessageViewConfiguration { import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
var isIncoming: Boolean
var isFirstFromSender: Boolean interface TimelineMessageLayoutRenderer {
var isLastFromSender: Boolean fun render(messageLayout: TimelineMessageLayout)
var showTimeAsOverlay: Boolean
var showNoBubble: Boolean
fun render()
} }

View file

@ -16,6 +16,7 @@
package im.vector.app.features.media package im.vector.app.features.media
import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
@ -23,6 +24,7 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
@ -109,7 +111,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
.into(imageView) .into(imageView)
} }
fun render(data: Data, mode: Mode, imageView: ImageView) { fun render(data: Data, mode: Mode, imageView: ImageView, cornerTransformation: Transformation<Bitmap> = RoundedCorners(dimensionConverter.dpToPx(8))) {
val size = processSize(data, mode) val size = processSize(data, mode)
imageView.updateLayoutParams { imageView.updateLayoutParams {
width = size.width width = size.width
@ -120,7 +122,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
createGlideRequest(data, mode, imageView, size) createGlideRequest(data, mode, imageView, size)
.dontAnimate() .dontAnimate()
.transform(RoundedCorners(dimensionConverter.dpToPx(8))) .transform(cornerTransformation)
// .thumbnail(0.3f) // .thumbnail(0.3f)
.into(imageView) .into(imageView)
} }

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/chat_bubble_corner_radius"/>
<gradient <gradient
android:type="linear" android:type="linear"
android:angle="270" android:angle="270"

View file

@ -3,18 +3,24 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:addStatesFromChildren="true"> android:addStatesFromChildren="true"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:paddingBottom="4dp">
<ViewStub <ViewStub
android:id="@+id/messageContentTextStub" android:id="@+id/messageContentTextStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_text_message_stub" android:layout="@layout/item_timeline_event_text_message_stub"
tools:visibility="visible" /> tools:visibility="visible" />
<ViewStub <ViewStub
android:id="@+id/messageContentCodeBlockStub" android:id="@+id/messageContentCodeBlockStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_code_block_stub" android:layout="@layout/item_timeline_event_code_block_stub"
tools:visibility="visible" /> tools:visibility="visible" />
@ -22,33 +28,34 @@
<ViewStub <ViewStub
android:id="@+id/messageContentMediaStub" android:id="@+id/messageContentMediaStub"
style="@style/TimelineContentStubBaseParams" style="@style/TimelineContentStubBaseParams"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inflatedId="@+id/messageContentMedia" android:inflatedId="@+id/messageContentMedia"
android:layout="@layout/item_timeline_event_media_message_stub" /> android:layout="@layout/item_timeline_event_media_message_stub" />
<ViewStub <ViewStub
android:id="@+id/messageContentFileStub" android:id="@+id/messageContentFileStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:visibility="gone" android:layout="@layout/item_timeline_event_file_stub"
android:layout="@layout/item_timeline_event_file_stub" /> tools:visibility="gone" />
<ViewStub <ViewStub
android:id="@+id/messageContentRedactedStub" android:id="@+id/messageContentRedactedStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_redacted_stub" /> android:layout="@layout/item_timeline_event_redacted_stub" />
<ViewStub <ViewStub
android:id="@+id/messageContentVoiceStub" android:id="@+id/messageContentVoiceStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_voice_stub" android:layout="@layout/item_timeline_event_voice_stub"
tools:visibility="gone" /> tools:visibility="gone" />
<ViewStub <ViewStub
android:id="@+id/messageContentPollStub" android:id="@+id/messageContentPollStub"
style="@style/TimelineContentStubBaseParams" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_poll" /> android:layout="@layout/item_timeline_event_poll" />

View file

@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:parentTag="android.widget.RelativeLayout" tools:parentTag="android.widget.RelativeLayout">
tools:viewBindingIgnore="true">
<im.vector.app.core.platform.CheckableView <im.vector.app.core.platform.CheckableView
android:id="@+id/messageSelectedBackground" android:id="@+id/messageSelectedBackground"
@ -91,7 +90,6 @@
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">