Fix alignment of read receipts for dual-side bubbles
Change-Id: I60d6dde73071fa28d069cba87850cd85d6ddd36b
This commit is contained in:
parent
d568523b8b
commit
221be6947d
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue