Fix alignment of read receipts for dual-side bubbles

Change-Id: I60d6dde73071fa28d069cba87850cd85d6ddd36b
This commit is contained in:
SpiritCroc 2021-04-14 17:22:34 +02:00
parent d568523b8b
commit 221be6947d
3 changed files with 115 additions and 39 deletions

View File

@ -0,0 +1,55 @@
package im.vector.app.core.ui.views
import android.content.Context
import android.view.ViewGroup
import androidx.core.view.children
import im.vector.app.features.themes.BubbleThemeUtils
interface BubbleDependentView<H> {
fun updateMessageBubble(context: Context, holder: H) {
val bubbleStyleSetting = BubbleThemeUtils.getBubbleStyle(context)
val bubbleStyle = when {
messageBubbleAllowed(context) -> {
bubbleStyleSetting
}
bubbleStyleSetting == BubbleThemeUtils.BUBBLE_STYLE_BOTH && pseudoBubbleAllowed() -> {
BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN
}
bubbleStyleSetting == BubbleThemeUtils.BUBBLE_STYLE_START && pseudoBubbleAllowed() -> {
BubbleThemeUtils.BUBBLE_STYLE_START_HIDDEN
}
else -> {
BubbleThemeUtils.BUBBLE_STYLE_NONE
}
}
val reverseBubble = shouldReverseBubble() && BubbleThemeUtils.drawsDualSide(bubbleStyle)
setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
}
fun messageBubbleAllowed(context: Context): Boolean {
return false
}
fun shouldReverseBubble(): Boolean {
return false
}
fun pseudoBubbleAllowed(): Boolean {
return false
}
fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean)
fun setFlatRtl(layout: ViewGroup, direction: Int, childDirection: Int, depth: Int = 1) {
layout.layoutDirection = direction
for (child in layout.children) {
if (depth > 1 && child is ViewGroup) {
setFlatRtl(child, direction, childDirection, depth-1)
} else {
child.layoutDirection = childDirection
}
}
}
}

View File

@ -31,13 +31,14 @@ import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.platform.CheckableView import im.vector.app.core.platform.CheckableView
import im.vector.app.core.ui.views.BubbleDependentView
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.themes.BubbleThemeUtils import im.vector.app.features.themes.BubbleThemeUtils
/** /**
* Children must override getViewType() * Children must override getViewType()
*/ */
abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>(), ItemWithEvents { abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>(), ItemWithEvents, BubbleDependentView<H> {
// To use for instance when opening a permalink with an eventId // To use for instance when opening a permalink with an eventId
@EpoxyAttribute @EpoxyAttribute
@ -61,7 +62,7 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
} }
holder.checkableBackground.isChecked = highlighted holder.checkableBackground.isChecked = highlighted
updateMessageBubble(holder) updateMessageBubble(holder.checkableBackground.context, holder)
} }
abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
@ -82,52 +83,20 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
return false return false
} }
protected fun setFlatRtl(layout: ViewGroup, direction: Int, childDirection: Int, depth: Int = 1) { override fun messageBubbleAllowed(context: Context): Boolean {
layout.layoutDirection = direction
for (child in layout.children) {
if (depth > 1 && child is ViewGroup) {
setFlatRtl(child, direction, childDirection, depth-1)
} else {
child.layoutDirection = childDirection
}
}
}
fun updateMessageBubble(holder: H) {
val bubbleStyleSetting = BubbleThemeUtils.getBubbleStyle(holder.checkableBackground.context)
val bubbleStyle = when {
messageBubbleAllowed(holder.checkableBackground.context) -> {
bubbleStyleSetting
}
bubbleStyleSetting == BubbleThemeUtils.BUBBLE_STYLE_BOTH && pseudoBubbleAllowed() -> {
BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN
}
bubbleStyleSetting == BubbleThemeUtils.BUBBLE_STYLE_START && pseudoBubbleAllowed() -> {
BubbleThemeUtils.BUBBLE_STYLE_START_HIDDEN
}
else -> {
BubbleThemeUtils.BUBBLE_STYLE_NONE
}
}
val reverseBubble = shouldReverseBubble() && BubbleThemeUtils.drawsDualSide(bubbleStyle)
setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
}
open fun messageBubbleAllowed(context: Context): Boolean {
return false return false
} }
open fun shouldReverseBubble(): Boolean { override fun shouldReverseBubble(): Boolean {
return false return false
} }
open fun pseudoBubbleAllowed(): Boolean { override fun pseudoBubbleAllowed(): Boolean {
return false return false
} }
@CallSuper @CallSuper
open fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) { override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
/* TODO-SC-merge: read receipt layout alignment /* TODO-SC-merge: read receipt layout alignment
val defaultDirection = holder.readReceiptsView.resources.configuration.layoutDirection; val defaultDirection = holder.readReceiptsView.resources.configuration.layoutDirection;
val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL

View File

@ -16,17 +16,24 @@
package im.vector.app.features.home.room.detail.timeline.item package im.vector.app.features.home.room.detail.timeline.item
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.RelativeLayout
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.ui.views.BubbleDependentView
import im.vector.app.core.ui.views.ReadReceiptsView import im.vector.app.core.ui.views.ReadReceiptsView
import im.vector.app.features.home.AvatarRenderer 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) @EpoxyModelClass(layout = R.layout.item_timeline_event_read_receipts)
abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(), ItemWithEvents { abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(), ItemWithEvents, BubbleDependentView<ReadReceiptsItem.Holder> {
@EpoxyAttribute lateinit var eventId: String @EpoxyAttribute lateinit var eventId: String
@EpoxyAttribute lateinit var readReceipts: List<ReadReceiptData> @EpoxyAttribute lateinit var readReceipts: List<ReadReceiptData>
@ -40,6 +47,8 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.readReceiptsView.render(readReceipts, avatarRenderer, clickListener) holder.readReceiptsView.render(readReceipts, avatarRenderer, clickListener)
updateMessageBubble(holder.readReceiptsView.context, holder)
} }
override fun unbind(holder: Holder) { override fun unbind(holder: Holder) {
@ -50,4 +59,47 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView) 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)
}
} }