From 043a908d24d7a1e3924927296e6b92f26e1bc979 Mon Sep 17 00:00:00 2001
From: SpiritCroc <dev@spiritcroc.de>
Date: Thu, 28 May 2020 12:56:28 +0200
Subject: [PATCH] Same side read receipts for both side bubbles

---
 .../detail/timeline/item/AbsMessageItem.kt    | 44 ++++--------
 .../detail/timeline/item/BaseEventItem.kt     | 67 +++++++++++++++++++
 .../res/layout/item_timeline_event_base.xml   | 11 +++
 .../layout/item_timeline_event_base_state.xml | 10 +++
 4 files changed, 101 insertions(+), 31 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt
index 02b32167c9..a686f36651 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -32,6 +32,7 @@ import androidx.annotation.IdRes
 import androidx.core.view.children
 import com.airbnb.epoxy.EpoxyAttribute
 import im.vector.riotx.R
+import im.vector.riotx.core.ui.views.ReadReceiptsView
 import im.vector.riotx.core.utils.DebouncedClickListener
 import im.vector.riotx.features.home.AvatarRenderer
 import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
@@ -116,7 +117,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
             }
         }
         holder.viewStubContainer.minimumWidth = getViewStubMinimumWidth(holder, contentInBubble, attributes.informationData.showInformation)
-        updateMessageBubble(holder)
     }
 
     abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
@@ -127,7 +127,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
         val bubbleView by bind<View>(R.id.bubbleView)
         val bubbleMemberNameView by bind<TextView>(R.id.bubbleMessageMemberNameView)
         val bubbleTimeView by bind<TextView>(R.id.bubbleMessageTimeView)
-        val informationBottom by bind<LinearLayout>(R.id.informationBottom)
         val viewStubContainer by bind<FrameLayout>(R.id.viewStubContainer)
     }
 
@@ -152,10 +151,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
         return infoInBubbles(context) && attributes.informationData.sentByMe
     }
 
-    open fun messageBubbleAllowed(context: Context): Boolean {
-        return false
-    }
-
     open fun getViewStubMinimumWidth(holder: H, contentInBubble: Boolean, showInformation: Boolean): Int {
         return if (contentInBubble && attributes.informationData.showInformation) {
             // Guess text width for name and time
@@ -168,13 +163,16 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
         }
     }
 
-    fun infoInBubbles(context: Context): Boolean {
+    private fun infoInBubbles(context: Context): Boolean {
         return messageBubbleAllowed(context) && BubbleThemeUtils.getBubbleStyle(context) == BubbleThemeUtils.BUBBLE_STYLE_BOTH
     }
 
-    fun updateMessageBubble(holder: H) {
-        val bubbleStyle = if (messageBubbleAllowed(holder.eventBaseView.context)) BubbleThemeUtils.getBubbleStyle(holder.eventBaseView.context) else BubbleThemeUtils.BUBBLE_STYLE_NONE
-        val reverseBubble = attributes.informationData.sentByMe && bubbleStyle == BubbleThemeUtils.BUBBLE_STYLE_BOTH
+    override fun shouldReverseBubble(): Boolean {
+        return attributes.informationData.sentByMe
+    }
+
+    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
@@ -232,32 +230,16 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
             }
         }
 
-        val defaultRtl = holder.eventBaseView.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL;
-        val shouldRtl = reverseBubble != defaultRtl
+        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
         /*
         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.informationBottom, if (shouldRtl) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR,
-                holder.eventBaseView.resources.configuration.layoutDirection, 2)
-    }
-
-    /*
-    open fun setRtl(rtl: Boolean) {
-        // TODO subclass overrides?
-    }
-     */
-
-    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
-            }
-        }
+        setFlatRtl(holder.reactionsContainer, if (reverseBubble) reverseDirection else defaultDirection,
+                holder.eventBaseView.resources.configuration.layoutDirection)
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
index aa28dd7b02..147c6dc6bc 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
@@ -16,11 +16,15 @@
 package im.vector.riotx.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.riotx.R
@@ -29,6 +33,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
 import im.vector.riotx.core.platform.CheckableView
 import im.vector.riotx.core.ui.views.ReadReceiptsView
 import im.vector.riotx.core.utils.DimensionConverter
+import im.vector.riotx.features.themes.BubbleThemeUtils
 
 /**
  * Children must override getViewType()
@@ -55,6 +60,8 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
             }
         }
         holder.checkableBackground.isChecked = highlighted
+
+        updateMessageBubble(holder)
     }
 
     /**
@@ -81,4 +88,64 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
     open fun ignoreMessageGuideline(context: Context): Boolean {
         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 = if (messageBubbleAllowed(holder.checkableBackground.context)) bubbleStyleSetting else BubbleThemeUtils.BUBBLE_STYLE_NONE
+        val reverseBubble = shouldReverseBubble() && bubbleStyle == BubbleThemeUtils.BUBBLE_STYLE_BOTH
+
+        setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
+    }
+
+    open fun messageBubbleAllowed(context: Context): Boolean {
+        return false
+    }
+
+    open fun shouldReverseBubble(): Boolean {
+        return false
+    }
+
+    @CallSuper
+    open fun setBubbleLayout(holder: H, 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 = bubbleStyleSetting == BubbleThemeUtils.BUBBLE_STYLE_BOTH
+
+        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)
+    }
+
 }
diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
index f557f14e1f..13f6ea8216 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -235,6 +235,17 @@
 
         </com.google.android.flexbox.FlexboxLayout>
 
+    </LinearLayout>
+
+
+    <LinearLayout
+        android:id="@+id/informationBottom2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/informationBottom"
+        android:layout_toEndOf="@id/messageStartGuideline"
+        android:orientation="vertical">
+
         <im.vector.riotx.core.ui.views.ReadReceiptsView
             android:id="@+id/readReceiptsView"
             android:layout_width="wrap_content"
diff --git a/vector/src/main/res/layout/item_timeline_event_base_state.xml b/vector/src/main/res/layout/item_timeline_event_base_state.xml
index 58496e1da4..504b61b71f 100644
--- a/vector/src/main/res/layout/item_timeline_event_base_state.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base_state.xml
@@ -105,6 +105,16 @@
 
         </com.google.android.flexbox.FlexboxLayout>
 
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/informationBottom2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/informationBottom"
+        android:layout_toEndOf="@id/messageStartGuideline"
+        android:orientation="vertical">
+
         <im.vector.riotx.core.ui.views.ReadReceiptsView
             android:id="@+id/readReceiptsView"
             android:layout_width="wrap_content"