[bubble merge] Revert Schildi bubbles, to be re-implemented

Change-Id: I86c8c1b7f5a0fad76d7fbec26eceabecacdf202c
This commit is contained in:
SpiritCroc 2022-02-19 11:20:33 +01:00
parent ecd28447c9
commit af202d22be
14 changed files with 303 additions and 985 deletions

View file

@ -7,6 +7,7 @@ import im.vector.app.features.themes.BubbleThemeUtils
interface BubbleDependentView<H> {
/*
fun messageBubbleAllowed(context: Context): Boolean {
return false
}
@ -20,8 +21,10 @@ interface BubbleDependentView<H> {
}
fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean)
*/
}
/*
// This function belongs to BubbleDependentView, but turned out to raise a NoSuchMethodError since recently
// when called from an onImageSizeUpdated listener
fun <H>updateMessageBubble(context: Context, view: BubbleDependentView<H>, holder: H) {
@ -55,3 +58,4 @@ fun setFlatRtl(layout: ViewGroup, direction: Int, childDirection: Int, depth: In
}
}
}
*/

View file

@ -31,10 +31,7 @@ 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.view.TimelineMessageLayoutRenderer
import im.vector.app.features.reactions.widget.ReactionButton
import im.vector.app.features.themes.BubbleThemeUtils
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.room.send.SendState
import kotlin.math.round
/**
* Base timeline item with reactions and read receipts.
@ -119,60 +116,6 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
failureIndicator?.isVisible = baseAttributes.informationData.sendState.hasFailed()
}
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
// ATTENTION: we go over the bubbleStyleSetting here: this might differ from the effective bubbleStyle
// for this view class! We want to use the setting to do some uniform alignments for all views though.
when (bubbleStyleSetting) {
BubbleThemeUtils.BUBBLE_STYLE_START,
BubbleThemeUtils.BUBBLE_STYLE_BOTH,
BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN,
BubbleThemeUtils.BUBBLE_STYLE_START_HIDDEN -> {
// Padding for views that align with the bubble (should be roughly the bubble tail width)
val bubbleStartAlignWidth = holder.informationBottom.resources.getDimensionPixelSize(R.dimen.sc_bubble_tail_size)
if (reverseBubble) {
// Align reactions container to bubble
holder.informationBottom.setPaddingRelative(
0,
0,
bubbleStartAlignWidth,
0
)
} else {
// Align reactions container to bubble
holder.informationBottom.setPaddingRelative(
bubbleStartAlignWidth,
0,
0,
0
)
}
}
else -> {
// No alignment padding for reactions required
holder.informationBottom.setPaddingRelative(0, 0, 0, 0)
}
}
if (BubbleThemeUtils.drawsDualSide(bubbleStyleSetting) /*&& baseAttributes.informationData.sentByMe*/) {
// Haven't found a good location for this yet for outgoing messages
holder.e2EDecorationView.render(null)
} else {
// Moved here from upstream's bind()
when (baseAttributes.informationData.e2eDecoration) {
E2EDecoration.NONE -> {
holder.e2EDecorationView.render(null)
}
E2EDecoration.WARN_IN_CLEAR,
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
holder.e2EDecorationView.render(RoomEncryptionTrustLevel.Warning)
}
}
}
}
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
val informationBottom by bind<ViewGroup>(R.id.informationBottom)

View file

@ -16,17 +16,10 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Typeface
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
@ -35,20 +28,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import kotlin.math.max
import kotlin.math.round
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.ui.views.SendStateImageView
import im.vector.app.core.ui.views.setFlatRtl
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.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.themes.BubbleThemeUtils
import im.vector.app.features.themes.ThemeUtils
import kotlin.math.ceil
import org.matrix.android.sdk.api.session.threads.ThreadDetails
import org.matrix.android.sdk.api.util.MatrixItem
@ -88,147 +75,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
override fun bind(holder: H) {
super.bind(holder)
if (true /* SC-TODO is SC layout */) {
val contentInBubble = infoInBubbles(holder.memberNameView.context)
val senderInBubble = senderNameInBubble(holder.memberNameView.context)
val avatarImageView: ImageView?
var memberNameView: TextView?
var timeView: TextView?
val hiddenViews = ArrayList<View>()
val invisibleViews = ArrayList<View>()
val canHideAvatar = canHideAvatars()
val canHideSender = canHideSender()
// Select which views are visible, based on bubble style and other criteria
if (attributes.informationData.messageLayout.showDisplayName) {
if (senderInBubble) {
memberNameView = holder.bubbleMemberNameView
hiddenViews.add(holder.memberNameView)
} else {
memberNameView = holder.memberNameView
hiddenViews.add(holder.bubbleMemberNameView)
}
if (contentInBubble) {
timeView = holder.bubbleTimeView
hiddenViews.add(holder.timeView)
} else {
timeView = holder.timeView
hiddenViews.add(holder.bubbleTimeView)
}
} else if (attributes.informationData.messageLayout.showTimestamp) {
memberNameView = null
//hiddenViews.add(holder.memberNameView) // this one get's some special hiding treatment below
hiddenViews.add(holder.bubbleMemberNameView)
if (contentInBubble) {
timeView = holder.bubbleTimeView
hiddenViews.add(holder.timeView)
hiddenViews.add(holder.memberNameView)
} else {
timeView = holder.timeView
hiddenViews.add(holder.bubbleTimeView)
// Set to INVISIBLE instead of adding to hiddenViews, which are set to GONE
// (upstream sets memberNameView.isInvisible = true here, which is effectively the same)
invisibleViews.add(holder.memberNameView)
}
} else {
memberNameView = null
hiddenViews.add(holder.memberNameView)
hiddenViews.add(holder.bubbleMemberNameView)
timeView = null
hiddenViews.add(holder.timeView)
hiddenViews.add(holder.bubbleTimeView)
}
if (timeView === holder.bubbleTimeView) {
// We have two possible bubble time view locations
// For code readability, we don't inline this setting in the above cases
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
timeView = holder.bubbleFooterTimeView
if (attributes.informationData.messageLayout.showDisplayName) {
if (canHideSender) {
// In the case of footer time, we can also hide the names without making it look awkward
if (memberNameView != null) {
hiddenViews.add(memberNameView)
memberNameView = null
}
hiddenViews.add(holder.bubbleTimeView)
} else if (!senderInBubble) {
// We don't need to reserve space here
hiddenViews.add(holder.bubbleTimeView)
} else {
// Don't completely remove, just hide, so our relative layout rules still work
invisibleViews.add(holder.bubbleTimeView)
}
} else {
// Do hide, or we accidentally reserve space
hiddenViews.add(holder.bubbleTimeView)
}
} else {
hiddenViews.add(holder.bubbleFooterTimeView)
}
}
// Dual-side bubbles: hide own avatar, and all in direct chats
if ((!attributes.informationData.messageLayout.showAvatar) ||
(contentInBubble && canHideAvatar)) {
avatarImageView = null
hiddenViews.add(holder.avatarImageView)
} else {
avatarImageView = holder.avatarImageView
}
// Views available in upstream Element
avatarImageView?.layoutParams = avatarImageView?.layoutParams?.apply {
height = attributes.avatarSize
width = attributes.avatarSize
}
avatarImageView?.visibility = View.VISIBLE
avatarImageView?.onClick(_avatarClickListener)
memberNameView?.visibility = View.VISIBLE
memberNameView?.onClick(_memberNameClickListener)
timeView?.visibility = View.VISIBLE
timeView?.text = attributes.informationData.time
memberNameView?.text = attributes.informationData.memberName
memberNameView?.setTextColor(attributes.getMemberNameColor())
if (avatarImageView != null) attributes.avatarRenderer.render(attributes.informationData.matrixItem, avatarImageView)
avatarImageView?.setOnLongClickListener(attributes.itemLongClickListener)
memberNameView?.setOnLongClickListener(attributes.itemLongClickListener)
// More extra views added by Schildi
if (senderInBubble) {
holder.viewStubContainer.minimumWidth = getViewStubMinimumWidth(holder, contentInBubble, attributes.informationData.messageLayout.showDisplayName)
} else {
holder.viewStubContainer.minimumWidth = 0
}
if (contentInBubble) {
holder.bubbleFootView.visibility = View.VISIBLE
} else {
hiddenViews.add(holder.bubbleFootView)
}
// Actually hide all unnecessary views
hiddenViews.forEach {
// Same as it.isVisible = false
it.visibility = View.GONE
}
invisibleViews.forEach {
// Same as it.isInvisible = true
it.visibility = View.INVISIBLE
}
// Render send state indicator
if (contentInBubble) {
// Bubbles have their own decoration in the anonymous read receipt (in the message footer)
holder.sendStateImageView.isVisible = false
holder.eventSendingIndicator.isVisible = false
} else {
holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
}
} else { // SC-TODO non-SC bubbles
if (attributes.informationData.messageLayout.showAvatar) {
holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
height = attributes.avatarSize
@ -263,7 +109,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
// Render send state indicator
holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
} // SC- TODO
// Threads
if (attributes.areThreadMessagesEnabled) {
@ -320,14 +165,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
val timeView by bind<TextView>(R.id.messageTimeView)
val eventBaseView by bind<RelativeLayout>(R.id.eventBaseView)
val bubbleView by bind<ViewGroup>(R.id.bubbleView)
val bubbleMemberNameView by bind<TextView>(R.id.bubbleMessageMemberNameView)
val bubbleTimeView by bind<TextView>(R.id.bubbleMessageTimeView)
val bubbleFootView by bind<LinearLayout>(R.id.bubbleFootView)
val bubbleFooterTimeView by bind<TextView>(R.id.bubbleFooterMessageTimeView)
val bubbleFooterReadReceipt by bind<ImageView>(R.id.bubbleFooterReadReceipt)
val viewStubContainer by bind<FrameLayout>(R.id.viewStubContainer)
val sendStateImageView by bind<SendStateImageView>(R.id.messageSendStateImageView)
val eventSendingIndicator by bind<ProgressBar>(R.id.eventSendingIndicator)
val threadSummaryConstraintLayout by bind<ConstraintLayout>(R.id.messageThreadSummaryConstraintLayout)
@ -382,380 +219,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
}
override fun ignoreMessageGuideline(context: Context): Boolean {
return infoInBubbles(context) && canHideAvatars()
return false // SC-TODO infoInBubbles(context) && canHideAvatars()
}
open fun getViewStubMinimumWidth(holder: H, contentInBubble: Boolean, showInformation: Boolean): Int {
val memberName = attributes.informationData.memberName.toString()
val time = attributes.informationData.time.toString()
return if (contentInBubble) {
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
if (attributes.informationData.messageLayout.showDisplayName && !canHideSender()) {
// Since timeView automatically gets enough space, either within or outside the viewStub, we just need to ensure the member name view has enough space
// Somehow not enough without extra space...
ceil(BubbleThemeUtils.guessTextWidth(holder.bubbleMemberNameView, "$memberName ")).toInt()
} else {
// wrap_content works!
0
}
} else if (attributes.informationData.messageLayout.showDisplayName || attributes.informationData.messageLayout.showTimestamp) {
// Guess text width for name and time next to each other
val text = if (attributes.informationData.messageLayout.showDisplayName) {
"$memberName $time"
} else {
time
}
val textSize = if (attributes.informationData.messageLayout.showDisplayName) {
max(holder.bubbleMemberNameView.textSize, holder.bubbleTimeView.textSize)
} else {
holder.bubbleTimeView.textSize
}
ceil(BubbleThemeUtils.guessTextWidth(textSize, text)).toInt()
} else {
// Not showing any header, use wrap_content of content only
0
}
} else {
0
}
}
private fun infoInBubbles(context: Context): Boolean {
return BubbleThemeUtils.getBubbleStyle(context) == BubbleThemeUtils.BUBBLE_STYLE_BOTH &&
(messageBubbleAllowed(context) || pseudoBubbleAllowed())
}
private fun senderNameInBubble(context: Context): Boolean {
return infoInBubbles(context) && !pseudoBubbleAllowed()
}
override fun shouldReverseBubble(): Boolean {
return attributes.informationData.sentByMe
}
open fun getBubbleMargin(resources: Resources, bubbleStyle: String, reverseBubble: Boolean): Int {
return when (bubbleStyle) {
BubbleThemeUtils.BUBBLE_STYLE_START,
BubbleThemeUtils.BUBBLE_STYLE_START_HIDDEN -> 0
// else: dual-side bubbles (getBubbleMargin should not get called for other bubbleStyles)
else -> {
when {
// Direct chats usually have avatars hidden on both sides
attributes.informationData.isDirect -> resources.getDimensionPixelSize(R.dimen.dual_bubble_both_sides_without_avatar_margin)
// No direct chat, but sent by me: other side has an avatar
attributes.informationData.sentByMe -> {
resources.getDimensionPixelSize(R.dimen.dual_bubble_one_side_without_avatar_margin) +
resources.getDimensionPixelSize(R.dimen.dual_bubble_one_side_avatar_offset) +
attributes.avatarSize
}
// No direct chat, sent by other: my side has hidden avatar
else -> resources.getDimensionPixelSize(R.dimen.dual_bubble_one_side_without_avatar_margin)
}
}
}
}
/**
* Whether to show the footer in front of the viewStub
*/
open fun allowFooterOverlay(holder: H): Boolean {
return false
}
/**
* Whether to show the footer aligned below the viewStub - requires enough width!
*/
open fun allowFooterBelow(holder: H): Boolean {
return true
}
open fun needsFooterReservation(holder: H): Boolean {
return false
}
open fun reserveFooterSpace(holder: H, width: Int, height: Int) {
}
private fun canHideAvatars(): Boolean {
return attributes.informationData.sentByMe || attributes.informationData.isDirect
}
private fun canHideSender(): Boolean {
return attributes.informationData.sentByMe ||
(attributes.informationData.isDirect && attributes.informationData.senderId == attributes.informationData.dmChatPartnerId)
}
protected fun getFooterMeasures(holder: H): Array<Int> {
val anonymousReadReceipt = BubbleThemeUtils.getVisibleAnonymousReadReceipts(holder.bubbleFootView.context,
attributes.informationData.readReceiptAnonymous, attributes.informationData.sentByMe)
return getFooterMeasures(holder, anonymousReadReceipt)
}
private fun getFooterMeasures(holder: H, anonymousReadReceipt: AnonymousReadReceipt): Array<Int> {
val timeWidth: Int
val timeHeight: Int
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
// Guess text width for name and time
timeWidth = ceil(BubbleThemeUtils.guessTextWidth(holder.bubbleFooterTimeView, attributes.informationData.time.toString())).toInt() + holder.bubbleFooterTimeView.paddingLeft + holder.bubbleFooterTimeView.paddingRight
timeHeight = ceil(holder.bubbleFooterTimeView.textSize).toInt() + holder.bubbleFooterTimeView.paddingTop + holder.bubbleFooterTimeView.paddingBottom
} else {
timeWidth = 0
timeHeight = 0
}
val readReceiptWidth: Int
val readReceiptHeight: Int
if (anonymousReadReceipt == AnonymousReadReceipt.NONE) {
readReceiptWidth = 0
readReceiptHeight = 0
} else {
readReceiptWidth = holder.bubbleFooterReadReceipt.maxWidth + holder.bubbleFooterReadReceipt.paddingLeft + holder.bubbleFooterReadReceipt.paddingRight
readReceiptHeight = holder.bubbleFooterReadReceipt.maxHeight + holder.bubbleFooterReadReceipt.paddingTop + holder.bubbleFooterReadReceipt.paddingBottom
}
var footerWidth = timeWidth + readReceiptWidth
var footerHeight = max(timeHeight, readReceiptHeight)
// Reserve extra padding, if we do have actual content
if (footerWidth > 0) {
footerWidth += holder.bubbleFootView.paddingLeft + holder.bubbleFootView.paddingRight
}
if (footerHeight > 0) {
footerHeight += holder.bubbleFootView.paddingTop + holder.bubbleFootView.paddingBottom
}
return arrayOf(footerWidth, footerHeight)
}
@SuppressLint("RtlHardcoded")
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
//val bubbleView = holder.eventBaseView
val bubbleView = holder.bubbleView
val contentInBubble = infoInBubbles(holder.memberNameView.context)
val defaultDirection = holder.eventBaseView.resources.configuration.layoutDirection;
val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL
val reverseDirection = if (defaultRtl) View.LAYOUT_DIRECTION_LTR else View.LAYOUT_DIRECTION_RTL
// Notice formatting - also relevant if no actual bubbles are shown
bubbleView.alpha = if (attributes.isNotice) 0.65f else 1f
when (bubbleStyle) {
BubbleThemeUtils.BUBBLE_STYLE_START,
BubbleThemeUtils.BUBBLE_STYLE_BOTH,
BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN,
BubbleThemeUtils.BUBBLE_STYLE_START_HIDDEN -> {
// Padding for bubble content: long for side with tail, short for other sides
val longPaddingDp: Int
val shortPaddingDp: Int
if (BubbleThemeUtils.drawsActualBubbles(bubbleStyle)) {
val bubbleRes = if (attributes.informationData.messageLayout.showAvatar) { // tail
if (reverseBubble) { // outgoing
R.drawable.msg_bubble_text_outgoing
} else { // incoming
R.drawable.msg_bubble_text_incoming
}
} else { // notail
if (reverseBubble) { // outgoing
R.drawable.msg_bubble_text_outgoing_notail
} else { // incoming
R.drawable.msg_bubble_text_incoming_notail
}
}
bubbleView.setBackgroundResource(bubbleRes)
longPaddingDp = bubbleView.resources.getDimensionPixelSize(R.dimen.sc_bubble_inner_padding_long_side)
shortPaddingDp = bubbleView.resources.getDimensionPixelSize(R.dimen.sc_bubble_inner_padding_short_side)
} else {
longPaddingDp = bubbleView.resources.getDimensionPixelSize(R.dimen.sc_bubble_tail_size)
shortPaddingDp = 0//if (attributes.informationData.showInformation && !hideSenderInformation()) { 8 } else { 0 }
}
if (reverseBubble != defaultRtl) {
// Use left/right instead of start/end: bubbleView is always LTR
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).leftMargin = getBubbleMargin(bubbleView.resources, bubbleStyle, reverseBubble)
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).rightMargin = 0
} else {
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).leftMargin = 0
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).rightMargin = getBubbleMargin(bubbleView.resources, bubbleStyle, reverseBubble)
}
if (reverseBubble != defaultRtl) {
// Use left/right instead of start/end: bubbleView is always LTR
bubbleView.setPadding(
shortPaddingDp,
shortPaddingDp,
longPaddingDp,
shortPaddingDp
)
} else {
bubbleView.setPadding(
longPaddingDp,
shortPaddingDp,
shortPaddingDp,
shortPaddingDp
)
}
if (contentInBubble) {
val anonymousReadReceipt = BubbleThemeUtils.getVisibleAnonymousReadReceipts(holder.bubbleFootView.context,
attributes.informationData.readReceiptAnonymous, attributes.informationData.sentByMe)
when (anonymousReadReceipt) {
AnonymousReadReceipt.PROCESSING -> {
holder.bubbleFooterReadReceipt.visibility = View.VISIBLE
holder.bubbleFooterReadReceipt.setImageResource(R.drawable.ic_processing_msg)
}
else -> {
holder.bubbleFooterReadReceipt.visibility = View.GONE
}
}
// We can't use end and start because of our weird layout RTL tricks
val alignEnd = if(defaultRtl) RelativeLayout.ALIGN_LEFT else RelativeLayout.ALIGN_RIGHT
val alignStart = if(defaultRtl) RelativeLayout.ALIGN_RIGHT else RelativeLayout.ALIGN_LEFT
val startOf = if(defaultRtl) RelativeLayout.RIGHT_OF else RelativeLayout.LEFT_OF
val endOf = if(defaultRtl) RelativeLayout.LEFT_OF else RelativeLayout.RIGHT_OF
val footerLayoutParams = holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams
var footerMarginStartDp = holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_margin_start)
var footerMarginEndDp = holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_margin_end)
if (allowFooterOverlay(holder)) {
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
footerLayoutParams.addRule(alignEnd, R.id.viewStubContainer)
footerLayoutParams.removeRule(alignStart)
footerLayoutParams.removeRule(RelativeLayout.BELOW)
footerLayoutParams.removeRule(endOf)
footerLayoutParams.removeRule(startOf)
if (needsFooterReservation(holder)) {
// Remove style used when not having reserved space
removeFooterOverlayStyle(holder)
// Calculate required footer space
val footerMeasures = getFooterMeasures(holder, anonymousReadReceipt)
val footerWidth = footerMeasures[0]
val footerHeight = footerMeasures[1]
reserveFooterSpace(holder, footerWidth, footerHeight)
} else {
// We have no reserved space -> style it to ensure readability on arbitrary backgrounds
styleFooterOverlay(holder)
}
} else {
when {
allowFooterBelow(holder) -> {
footerLayoutParams.addRule(RelativeLayout.BELOW, R.id.viewStubContainer)
footerLayoutParams.addRule(alignEnd, R.id.viewStubContainer)
footerLayoutParams.removeRule(alignStart)
footerLayoutParams.removeRule(RelativeLayout.ALIGN_BOTTOM)
footerLayoutParams.removeRule(endOf)
footerLayoutParams.removeRule(startOf)
footerLayoutParams.removeRule(RelativeLayout.START_OF)
}
reverseBubble -> /* force footer on the left / at the start */ {
footerLayoutParams.addRule(RelativeLayout.START_OF, R.id.viewStubContainer)
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
footerLayoutParams.removeRule(alignEnd)
footerLayoutParams.removeRule(alignStart)
footerLayoutParams.removeRule(endOf)
footerLayoutParams.removeRule(startOf)
footerLayoutParams.removeRule(RelativeLayout.BELOW)
// Reverse margins
footerMarginStartDp = holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_reverse_margin_start)
footerMarginEndDp = holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_reverse_margin_end)
}
else -> /* footer on the right / at the end */ {
footerLayoutParams.addRule(endOf, R.id.viewStubContainer)
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
footerLayoutParams.removeRule(startOf)
footerLayoutParams.removeRule(alignEnd)
footerLayoutParams.removeRule(alignStart)
footerLayoutParams.removeRule(RelativeLayout.BELOW)
footerLayoutParams.removeRule(RelativeLayout.START_OF)
}
}
removeFooterOverlayStyle(holder)
}
if (defaultRtl) {
footerLayoutParams.rightMargin = footerMarginStartDp
footerLayoutParams.leftMargin = footerMarginEndDp
holder.bubbleMemberNameView.gravity = Gravity.RIGHT
} else {
footerLayoutParams.leftMargin = footerMarginStartDp
footerLayoutParams.rightMargin = footerMarginEndDp
holder.bubbleMemberNameView.gravity = Gravity.LEFT
}
}
if (bubbleStyle == BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN) {
// We need to align the non-bubble member name view to pseudo bubbles
if (reverseBubble) {
holder.memberNameView.setPaddingRelative(
shortPaddingDp,
0,
longPaddingDp,
0
)
} else {
holder.memberNameView.setPaddingRelative(
longPaddingDp,
0,
shortPaddingDp,
0
)
}
}
}
//BubbleThemeUtils.BUBBLE_STYLE_NONE,
else -> {
bubbleView.background = null
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).marginStart = 0
(bubbleView.layoutParams as ViewGroup.MarginLayoutParams).marginEnd = 0
/*
(bubbleView.layoutParams as RelativeLayout.LayoutParams).marginStart = 0
(bubbleView.layoutParams as RelativeLayout.LayoutParams).topMargin = 0
(bubbleView.layoutParams as RelativeLayout.LayoutParams).bottomMargin = 0
*/
bubbleView.setPadding(0, 0, 0, 0)
holder.memberNameView.setPadding(0, 0, 0, 0)
}
}
/*
holder.eventBaseView.layoutDirection = if (shouldRtl) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
setRtl(shouldRtl)
*/
(holder.bubbleView.layoutParams as FrameLayout.LayoutParams).gravity = if (reverseBubble) Gravity.END else Gravity.START
//holder.informationBottom.layoutDirection = if (shouldRtl) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
setFlatRtl(holder.reactionsContainer, if (reverseBubble) reverseDirection else defaultDirection,
holder.eventBaseView.resources.configuration.layoutDirection)
// Layout is broken if bubbleView is RTL (for some reason, Android uses left/end padding for right/start as well...)
setFlatRtl(holder.bubbleView, View.LAYOUT_DIRECTION_LTR,
holder.eventBaseView.resources.configuration.layoutDirection)
}
private fun tintFooter(holder: H, color: Int) {
val tintList = ColorStateList(arrayOf(intArrayOf(0)), intArrayOf(color))
holder.bubbleFooterReadReceipt.imageTintList = tintList
holder.bubbleFooterTimeView.setTextColor(tintList)
}
private fun styleFooterOverlay(holder: H) {
holder.bubbleFootView.setBackgroundResource(R.drawable.timestamp_overlay)
tintFooter(holder, ThemeUtils.getColor(holder.bubbleFootView.context, R.attr.timestamp_overlay_fg))
val padding = holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_overlay_padding)
holder.bubbleFootView.setPaddingRelative(
padding,
padding,
// compensate from inner view padding on the other side
padding + holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_padding_compensation),
padding
)
}
private fun removeFooterOverlayStyle(holder: H) {
holder.bubbleFootView.background = null
tintFooter(holder, ThemeUtils.getColor(holder.bubbleFootView.context, R.attr.vctr_content_secondary))
holder.bubbleFootView.setPaddingRelative(
0,
holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_noverlay_padding_top),
0,
holder.bubbleFootView.resources.getDimensionPixelSize(R.dimen.sc_footer_noverlay_padding_bottom)
)
}
}

View file

@ -16,15 +16,11 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.annotation.CallSuper
import androidx.annotation.IdRes
import androidx.core.view.children
import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import im.vector.app.R
@ -32,8 +28,6 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.platform.CheckableView
import im.vector.app.core.ui.views.BubbleDependentView
import im.vector.app.core.ui.views.updateMessageBubble
import im.vector.app.features.themes.BubbleThemeUtils
/**
* Children must override getViewType()
@ -71,8 +65,6 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
}
}
holder.checkableBackground.isChecked = highlighted
updateMessageBubble(holder.checkableBackground.context, holder)
}
abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
@ -93,55 +85,4 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
open fun ignoreMessageGuideline(context: Context): Boolean {
return false
}
override fun messageBubbleAllowed(context: Context): Boolean {
return false
}
override fun shouldReverseBubble(): Boolean {
return false
}
override fun pseudoBubbleAllowed(): Boolean {
return false
}
@CallSuper
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
/* TODO-SC-merge: read receipt layout alignment
val defaultDirection = holder.readReceiptsView.resources.configuration.layoutDirection;
val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL
val reverseDirection = if (defaultRtl) View.LAYOUT_DIRECTION_LTR else View.LAYOUT_DIRECTION_RTL
// Always keep read receipts of others on other side for dual side bubbles
val dualBubbles = BubbleThemeUtils.drawsDualSide(bubbleStyleSetting)
val receiptParent = holder.readReceiptsView.parent
if (receiptParent is LinearLayout) {
(holder.readReceiptsView.layoutParams as LinearLayout.LayoutParams).gravity = if (dualBubbles) Gravity.START else Gravity.END
(receiptParent.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.END_OF)
(receiptParent.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_PARENT_START)
if (dualBubbles) {
(receiptParent.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE)
} else {
(receiptParent.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.END_OF, R.id.messageStartGuideline)
}
} else {
if (dualBubbles) {
(holder.readReceiptsView.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_PARENT_END)
} else {
(holder.readReceiptsView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_END)
}
}
// Also set rtl to have members fill from the natural side
setFlatRtl(holder.readReceiptsView, if (dualBubbles) reverseDirection else defaultDirection, defaultDirection)
*/
}
fun updateMessageBubble(context: Context, holder: H) {
return updateMessageBubble(context, this, holder)
}
}

View file

@ -16,7 +16,6 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Paint
@ -28,15 +27,12 @@ import androidx.annotation.DrawableRes
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import kotlin.math.max
import im.vector.app.R
import im.vector.app.core.epoxy.onClick
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
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.themes.ThemeUtils
import im.vector.app.features.themes.BubbleThemeUtils
import kotlin.math.ceil
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@ -109,24 +105,6 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
override fun getViewStubId() = STUB_ID
override fun messageBubbleAllowed(context: Context): Boolean {
return true
}
override fun getViewStubMinimumWidth(holder: Holder, contentInBubble: Boolean, showInformation: Boolean): Int {
val superVal = super.getViewStubMinimumWidth(holder, contentInBubble, showInformation)
// Guess text width for name and time
// On first call, holder.fileImageView.width is not initialized yet
val imageWidth = holder.fileImageView.resources.getDimensionPixelSize(R.dimen.chat_avatar_size)
val minimumWidthWithText =
ceil(BubbleThemeUtils.guessTextWidth(holder.filenameView, filename)).toInt() +
imageWidth +
holder.filenameView.resources.getDimensionPixelSize(R.dimen.sc_bubble_guess_minimum_width_padding)
val absoluteMinimumWidth = imageWidth*3
return max(max(absoluteMinimumWidth, minimumWidthWithText), superVal)
}
class Holder : AbsMessageItem.Holder(STUB_ID) {
val mainLayout by bind<ViewGroup>(R.id.messageFileMainLayout)
val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)

View file

@ -16,12 +16,9 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.content.res.Resources
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
@ -37,7 +34,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.themes.BubbleThemeUtils
import org.matrix.android.sdk.api.util.MimeTypes
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@ -61,6 +57,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
@EpoxyAttribute
lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
// SC-TODO
var lastAllowedFooterOverlay: Boolean = true
var lastShowFooterBellow: Boolean = true
var forceAllowFooterOverlay: Boolean? = null
@ -73,6 +70,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
val onImageSizeListener = object: ImageContentRenderer.OnImageSizeListener {
override fun onImageSizeUpdated(width: Int, height: Int) {
// Image size change -> different footer space situation possible
/* SC-TODO
val footerMeasures = getFooterMeasures(holder)
forceAllowFooterOverlay = shouldAllowFooterOverlay(footerMeasures, width, height)
val newShowFooterBellow = shouldShowFooterBellow(footerMeasures, width, height)
@ -80,6 +78,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
showFooterBellow = newShowFooterBellow
updateMessageBubble(holder.imageView.context, holder)
}
*/
}
}
val animate = mediaData.mimeType == MimeTypes.Gif
@ -123,43 +122,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
override fun getViewStubId() = STUB_ID
override fun messageBubbleAllowed(context: Context): Boolean {
return false
}
override fun pseudoBubbleAllowed(): Boolean {
return true
}
override fun getBubbleMargin(resources: Resources, bubbleStyle: String, reverseBubble: Boolean): Int {
return 0
}
override fun setBubbleLayout(holder: Holder, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
// Case: ImageContentRenderer.processSize only sees width=height=0 -> width of the ImageView not adapted to the actual content
// -> Align image within ImageView to same side as message bubbles
holder.imageView.scaleType = if (reverseBubble) ImageView.ScaleType.FIT_END else ImageView.ScaleType.FIT_START
// Case: Message information (sender name + date) makes the containing view wider than the ImageView
// -> Align ImageView within its parent to the same side as message bubbles
(holder.imageView.layoutParams as ConstraintLayout.LayoutParams).horizontalBias = if (reverseBubble) 1f else 0f
// Image outline
when {
bubbleStyle == BubbleThemeUtils.BUBBLE_STYLE_NONE || mode != ImageContentRenderer.Mode.THUMBNAIL -> {
// Don't show it for non-bubble layouts, don't show for Stickers, ...
holder.mediaContentView.background = null
}
attributes.informationData.sentByMe -> {
holder.mediaContentView.setBackgroundResource(R.drawable.background_image_border_outgoing)
}
else -> {
holder.mediaContentView.setBackgroundResource(R.drawable.background_image_border_incoming)
}
}
}
// SC-TODO
private fun shouldAllowFooterOverlay(footerMeasures: Array<Int>, imageWidth: Int, imageHeight: Int): Boolean {
val footerWidth = footerMeasures[0]
val footerHeight = footerMeasures[1]
@ -168,6 +131,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
return imageWidth > 1.5*footerWidth && imageHeight > 1.5*footerHeight && (imageWidth * imageHeight > 4 * footerWidth * footerHeight)
}
// SC-TODO
private fun shouldShowFooterBellow(footerMeasures: Array<Int>, imageWidth: Int, imageHeight: Int): Boolean {
// Only show footer bellow if the width is not the limiting factor (or it will get cut).
// Otherwise, we can not be sure in this place that we'll have enough space on the side
@ -177,32 +141,6 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
return imageWidth > 1.5*footerWidth && imageHeight < 1.5*footerHeight
}
override fun allowFooterOverlay(holder: Holder): Boolean {
val rememberedAllowFooterOverlay = forceAllowFooterOverlay
if (rememberedAllowFooterOverlay != null) {
lastAllowedFooterOverlay = rememberedAllowFooterOverlay
return rememberedAllowFooterOverlay
}
val imageWidth = holder.imageView.width
val imageHeight = holder.imageView.height
if (imageWidth == 0 && imageHeight == 0) {
// Not initialised yet, assume true
lastAllowedFooterOverlay = true
return true
}
// If the footer covers most of the image, or is even larger than the image, move it outside
val footerMeasures = getFooterMeasures(holder)
lastAllowedFooterOverlay = shouldAllowFooterOverlay(footerMeasures, imageWidth, imageHeight)
return lastAllowedFooterOverlay
}
override fun allowFooterBelow(holder: Holder): Boolean {
val showBellow = showFooterBellow
lastShowFooterBellow = showBellow
return showBellow
}
class Holder : AbsMessageItem.Holder(STUB_ID) {
val progressLayout by bind<ViewGroup>(R.id.messageMediaUploadProgressLayout)

View file

@ -16,13 +16,10 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.text.Spanned
import android.text.TextUtils
import android.text.method.MovementMethod
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.text.PrecomputedTextCompat
import androidx.core.view.isVisible
import androidx.core.widget.TextViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
@ -180,36 +177,4 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
companion object {
private const val STUB_ID = R.id.messageContentTextStub
}
override fun messageBubbleAllowed(context: Context): Boolean {
return true
}
override fun allowFooterOverlay(holder: Holder): Boolean {
return true
}
override fun needsFooterReservation(holder: Holder): Boolean {
return true
}
override fun reserveFooterSpace(holder: Holder, width: Int, height: Int) {
if (attributes.informationData.messageLayout !is TimelineMessageLayout.ScBubble) {
return
}
// Remember for PreviewUrlViewUpdater.onStateUpdated
footerWidth = width
footerHeight = height
// Reserve both in preview and in message
// User might close preview, so we still need place in the message
// if we don't want to change this afterwards
// This might be a race condition, but the UI-isssue if evaluated wrongly is negligible
if (!holder.previewUrlViewSc.isVisible) {
holder.messageView.footerWidth = width
holder.messageView.footerHeight = height
} // else: will be handled in onStateUpdated
holder.previewUrlViewSc.footerWidth = height
holder.previewUrlViewSc.footerHeight = height
holder.previewUrlViewSc.updateFooterSpace()
}
}

View file

@ -16,7 +16,6 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.format.DateUtils
@ -24,7 +23,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
@ -37,7 +35,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadSt
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.themes.BubbleThemeUtils
import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@ -164,18 +161,4 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
companion object {
private const val STUB_ID = R.id.messageContentVoiceStub
}
override fun messageBubbleAllowed(context: Context): Boolean {
return true
}
override fun setBubbleLayout(holder: Holder, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
if (BubbleThemeUtils.drawsActualBubbles(bubbleStyle)) {
(holder.voiceLayout.layoutParams as ViewGroup.MarginLayoutParams).marginEnd = 0
} else {
(holder.voiceLayout.layoutParams as ViewGroup.MarginLayoutParams).marginEnd = holder.voiceLayout.resources.getDimensionPixelSize(R.dimen.no_bubble_margin_end)
}
}
}

View file

@ -16,7 +16,6 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.children
@ -90,8 +89,4 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
companion object {
private const val STUB_ID = R.id.messageContentPollStub
}
override fun messageBubbleAllowed(context: Context): Boolean {
return true
}
}

View file

@ -16,12 +16,6 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
@ -32,11 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.ui.views.BubbleDependentView
import im.vector.app.core.ui.views.ReadReceiptsView
import im.vector.app.core.ui.views.setFlatRtl
import im.vector.app.core.ui.views.updateMessageBubble
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.themes.BubbleThemeUtils
import timber.log.Timber
@EpoxyModelClass(layout = R.layout.item_timeline_event_read_receipts)
abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(), ItemWithEvents, BubbleDependentView<ReadReceiptsItem.Holder> {
@ -56,8 +46,6 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
holder.readReceiptsView.onClick(clickListener)
holder.readReceiptsView.render(readReceipts, avatarRenderer)
updateMessageBubble(holder.readReceiptsView.context, holder)
holder.readReceiptsView.isVisible = !shouldHideReadReceipts
}
@ -70,51 +58,4 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
}
override fun setBubbleLayout(holder: Holder, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
val defaultDirection = holder.readReceiptsView.resources.configuration.layoutDirection;
val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL
val reverseDirection = if (defaultRtl) View.LAYOUT_DIRECTION_LTR else View.LAYOUT_DIRECTION_RTL
// Always keep read receipts of others on other side for dual side bubbles
val dualBubbles = BubbleThemeUtils.drawsDualSide(bubbleStyleSetting)
/*
val receiptParent = holder.readReceiptsView.parent
if (receiptParent is LinearLayout) {
(holder.readReceiptsView.layoutParams as LinearLayout.LayoutParams).gravity = if (dualBubbles) Gravity.START else Gravity.END
(receiptParent.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.END_OF)
(receiptParent.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_PARENT_START)
if (dualBubbles) {
(receiptParent.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE)
} else {
(receiptParent.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.END_OF, R.id.messageStartGuideline)
}
} else if (receiptParent is RelativeLayout) {
if (dualBubbles) {
(holder.readReceiptsView.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_PARENT_END)
} else {
(holder.readReceiptsView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_END)
}
} else if (receiptParent is FrameLayout) {
*/
if (dualBubbles) {
(holder.readReceiptsView.layoutParams as FrameLayout.LayoutParams).gravity = Gravity.START
} else {
(holder.readReceiptsView.layoutParams as FrameLayout.LayoutParams).gravity = Gravity.END
}
/*
} else {
Timber.e("Unsupported layout for read receipts parent: $receiptParent")
}
*/
// Also set rtl to have members fill from the natural side
setFlatRtl(holder.readReceiptsView, if (dualBubbles) reverseDirection else defaultDirection, defaultDirection)
}
fun updateMessageBubble(context: Context, holder: Holder) {
return updateMessageBubble(context, this, holder)
}
}

View file

@ -16,10 +16,8 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.features.themes.BubbleThemeUtils
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>() {
@ -33,8 +31,4 @@ abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>(
companion object {
private const val STUB_ID = R.id.messageContentRedactedStub
}
override fun messageBubbleAllowed(context: Context): Boolean {
return BubbleThemeUtils.getBubbleStyle(context) == BubbleThemeUtils.BUBBLE_STYLE_BOTH
}
}

View file

@ -23,7 +23,6 @@ class TimelineLayoutSettingsProvider @Inject constructor(private val vectorPrefe
fun getLayoutSettings(): TimelineLayoutSettings {
// SC-TODO
if (true) return TimelineLayoutSettings.SC_BUBBLE
return if (vectorPreferences.useMessageBubblesLayout()) {
TimelineLayoutSettings.BUBBLE
} else {

View file

@ -2,7 +2,6 @@
<RelativeLayout 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:id="@+id/eventBaseView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
@ -72,142 +71,35 @@
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignTop="@id/bubbleWrapper"
android:layout_alignTop="@id/viewStubContainer"
android:layout_alignEnd="@id/decorationSpace"
android:layout_marginTop="7dp"
android:visibility="gone"
tools:visibility="visible" />
<!-- bubble wrapper for controlling gravity -->
<FrameLayout
android:id="@+id/bubbleWrapper"
android:layout_width="match_parent"
<include
android:id="@+id/viewStubContainer"
layout="@layout/item_timeline_event_view_stubs_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/messageMemberNameView"
android:layout_marginEnd="8dp"
android:layout_toStartOf="@id/messageSendStateImageView"
android:layout_toEndOf="@id/messageStartGuideline"
android:layout_marginHorizontal="8dp"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/bubbleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginVertical="4dp"
android:clipChildren="false"
android:clipToPadding="false"
tools:background="@drawable/msg_bubble_text_incoming"
tools:ignore="UselessParent">
<!--
<im.vector.app.core.platform.EllipsizingTextView
-->
<TextView
android:id="@+id/bubbleMessageMemberNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/bubbleMessageTimeView"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textSize="15sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/bubbleMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/bubbleMessageMemberNameView"
android:layout_alignEnd="@id/viewStubContainer"
android:layout_marginBottom="4dp"
android:maxLines="1"
android:textColor="?vctr_content_secondary"
android:textSize="12sp"
tools:text="@tools:sample/date/hhmm" />
<include
android:id="@+id/viewStubContainer"
layout="@layout/item_timeline_event_view_stubs_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/bubbleMessageTimeView"
android:layout_margin="0dp"
android:addStatesFromChildren="true" />
<!--
<TextView
android:id="@+id/bubbleMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/viewStubContainer"
android:layout_alignEnd="@id/viewStubContainer"
android:layout_marginStart="8dp"
android:layout_marginEnd="0dp"
android:maxLines="1"
android:textColor="?vctr_content_secondary"
android:textSize="12sp"
tools:text="@tools:sample/date/hhmm" />
-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginBottom="1dp"
tools:layout_marginStart="4dp"
tools:layout_marginEnd="1dp"
tools:layout_alignBottom="@id/viewStubContainer"
tools:layout_alignEnd="@id/viewStubContainer"
tools:paddingTop="@dimen/sc_footer_noverlay_padding_top"
tools:paddingBottom="@dimen/sc_footer_noverlay_padding_bottom"
android:id="@+id/bubbleFootView">
<TextView
android:id="@+id/bubbleFooterMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:maxLines="1"
tools:textColor="?vctr_content_secondary"
android:textSize="12sp"
android:layout_gravity="bottom"
tools:text="@tools:sample/date/hhmm" />
<!-- We read maxWidth and maxHeight from code to guess footer size -->
<ImageView
android:id="@+id/bubbleFooterReadReceipt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="16dp"
android:maxHeight="16dp"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:layout_gravity="bottom"
tools:tint="?vctr_content_secondary"
tools:src="@drawable/ic_processing_msg"
android:contentDescription="@string/footer_read_receipt_content_description" />
</LinearLayout>
</RelativeLayout>
</FrameLayout>
android:addStatesFromChildren="true" />
<im.vector.app.core.ui.views.SendStateImageView
android:id="@+id/messageSendStateImageView"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignBottom="@id/viewStubContainer"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/event_status_a11y_sending"
android:src="@drawable/ic_sending_message"
android:visibility="gone"
android:visibility="invisible"
tools:tint="?vctr_content_tertiary"
tools:visibility="visible" />
@ -215,7 +107,7 @@
android:id="@+id/eventSendingIndicator"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignBottom="@id/viewStubContainer"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
@ -230,7 +122,7 @@
android:id="@+id/informationBottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bubbleWrapper"
android:layout_below="@id/viewStubContainer"
android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true"
android:orientation="vertical">
@ -278,4 +170,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</RelativeLayout>

View file

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/eventBaseView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:background="?attr/selectableItemBackground"
tools:viewBindingIgnore="true">
<im.vector.app.core.platform.CheckableView
android:id="@+id/messageSelectedBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@id/informationBottom"
android:layout_alignParentTop="true"
android:background="@drawable/highlighted_message_background" />
<ImageView
android:id="@+id/messageAvatarImageView"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:contentDescription="@string/avatar"
tools:src="@sample/user_round_avatars" />
<TextView
android:id="@+id/messageMemberNameView"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:textAlignment="viewStart"
android:layout_toStartOf="@id/messageTimeView"
android:layout_toEndOf="@id/messageStartGuideline"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
tools:text="@sample/users.json/data/displayName" />
<TextView
android:id="@+id/messageTimeView"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/messageMemberNameView"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:textColor="?vctr_content_secondary"
tools:text="@tools:sample/date/hhmm" />
<View
android:id="@+id/messageStartGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
tools:layout_marginStart="52dp" />
<Space
android:id="@+id/decorationSpace"
android:layout_width="4dp"
android:layout_height="8dp"
android:layout_toEndOf="@id/messageStartGuideline" />
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignTop="@id/bubbleWrapper"
android:layout_alignEnd="@id/decorationSpace"
android:layout_marginTop="7dp"
android:visibility="gone"
tools:visibility="visible" />
<!-- bubble wrapper for controlling gravity -->
<FrameLayout
android:id="@+id/bubbleWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/messageMemberNameView"
android:layout_toStartOf="@id/messageSendStateImageView"
android:layout_toEndOf="@id/messageStartGuideline"
android:layout_marginHorizontal="8dp"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/bubbleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginVertical="4dp"
android:clipChildren="false"
android:clipToPadding="false"
tools:background="@drawable/msg_bubble_text_incoming"
tools:ignore="UselessParent">
<!--
<im.vector.app.core.platform.EllipsizingTextView
-->
<TextView
android:id="@+id/bubbleMessageMemberNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/bubbleMessageTimeView"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textSize="15sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/bubbleMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/bubbleMessageMemberNameView"
android:layout_alignEnd="@id/viewStubContainer"
android:layout_marginBottom="4dp"
android:maxLines="1"
android:textColor="?vctr_content_secondary"
android:textSize="12sp"
tools:text="@tools:sample/date/hhmm" />
<include
android:id="@+id/viewStubContainer"
layout="@layout/item_timeline_event_view_stubs_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/bubbleMessageTimeView"
android:layout_margin="0dp"
android:addStatesFromChildren="true" />
<!--
<TextView
android:id="@+id/bubbleMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/viewStubContainer"
android:layout_alignEnd="@id/viewStubContainer"
android:layout_marginStart="8dp"
android:layout_marginEnd="0dp"
android:maxLines="1"
android:textColor="?vctr_content_secondary"
android:textSize="12sp"
tools:text="@tools:sample/date/hhmm" />
-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginBottom="1dp"
tools:layout_marginStart="4dp"
tools:layout_marginEnd="1dp"
tools:layout_alignBottom="@id/viewStubContainer"
tools:layout_alignEnd="@id/viewStubContainer"
tools:paddingTop="@dimen/sc_footer_noverlay_padding_top"
tools:paddingBottom="@dimen/sc_footer_noverlay_padding_bottom"
android:id="@+id/bubbleFootView">
<TextView
android:id="@+id/bubbleFooterMessageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:maxLines="1"
tools:textColor="?vctr_content_secondary"
android:textSize="12sp"
android:layout_gravity="bottom"
tools:text="@tools:sample/date/hhmm" />
<!-- We read maxWidth and maxHeight from code to guess footer size -->
<ImageView
android:id="@+id/bubbleFooterReadReceipt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="16dp"
android:maxHeight="16dp"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:layout_gravity="bottom"
tools:tint="?vctr_content_secondary"
tools:src="@drawable/ic_processing_msg"
android:contentDescription="@string/footer_read_receipt_content_description" />
</LinearLayout>
</RelativeLayout>
</FrameLayout>
<im.vector.app.core.ui.views.SendStateImageView
android:id="@+id/messageSendStateImageView"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/event_status_a11y_sending"
android:src="@drawable/ic_sending_message"
android:visibility="gone"
tools:tint="?vctr_content_tertiary"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/eventSendingIndicator"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:indeterminateTint="?vctr_content_secondary"
android:visibility="gone"
app:tint="?vctr_content_tertiary"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/informationBottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bubbleWrapper"
android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true"
android:orientation="vertical">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/reactionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
app:dividerDrawable="@drawable/reaction_divider"
app:flexWrap="wrap"
app:showDivider="middle"
tools:background="#F0E0F0"
tools:layout_height="40dp">
<!-- ReactionButtons will be added here in the code -->
<!--im.vector.app.features.reactions.widget.ReactionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" /-->
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/messageThreadSummaryConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/informationBottom"
android:layout_marginEnd="32dp"
android:layout_marginBottom="4dp"
android:paddingStart="13dp"
android:paddingEnd="13dp"
android:layout_toEndOf="@id/messageStartGuideline"
android:background="@drawable/rounded_rect_shape_8"
android:contentDescription="@string/room_threads_filter"
android:maxWidth="496dp"
android:minWidth="144dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<include layout="@layout/view_thread_room_summary" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>