diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BubbleDependentView.kt b/vector/src/main/java/im/vector/app/core/ui/views/BubbleDependentView.kt
new file mode 100644
index 0000000000..68c4243add
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/views/BubbleDependentView.kt
@@ -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
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
index 041cec85e1..1e7a01c384 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
@@ -31,13 +31,14 @@ import im.vector.app.R
 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.utils.DimensionConverter
 import im.vector.app.features.themes.BubbleThemeUtils
 
 /**
  * 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
     @EpoxyAttribute
@@ -61,7 +62,7 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
         }
         holder.checkableBackground.isChecked = highlighted
 
-        updateMessageBubble(holder)
+        updateMessageBubble(holder.checkableBackground.context, holder)
     }
 
     abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
@@ -82,52 +83,20 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
         return false
     }
 
-    protected 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
-            }
-        }
-    }
-
-    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 {
+    override fun messageBubbleAllowed(context: Context): Boolean {
         return false
     }
 
-    open fun shouldReverseBubble(): Boolean {
+    override fun shouldReverseBubble(): Boolean {
         return false
     }
 
-    open fun pseudoBubbleAllowed(): Boolean {
+    override fun pseudoBubbleAllowed(): Boolean {
         return false
     }
 
     @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
         val defaultDirection = holder.readReceiptsView.resources.configuration.layoutDirection;
         val defaultRtl = defaultDirection == View.LAYOUT_DIRECTION_RTL
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
index b88afb0598..99e7dfc537 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
@@ -16,17 +16,24 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.view.Gravity
 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.EpoxyModelClass
 import com.airbnb.epoxy.EpoxyModelWithHolder
 import im.vector.app.R
 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.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 {
+abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(), ItemWithEvents, BubbleDependentView<ReadReceiptsItem.Holder> {
 
     @EpoxyAttribute lateinit var eventId: String
     @EpoxyAttribute lateinit var readReceipts: List<ReadReceiptData>
@@ -40,6 +47,8 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
     override fun bind(holder: Holder) {
         super.bind(holder)
         holder.readReceiptsView.render(readReceipts, avatarRenderer, clickListener)
+
+        updateMessageBubble(holder.readReceiptsView.context, holder)
     }
 
     override fun unbind(holder: Holder) {
@@ -50,4 +59,47 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder<ReadReceiptsItem.Holder>(
     class Holder : VectorEpoxyHolder() {
         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)
+    }
 }