From 9a5934dd33ce66353e63d34206d16ec9a6cdba7f Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 16 Dec 2021 20:57:05 +0100
Subject: [PATCH 01/36] Bubbles: R&D try to find the best way to provide
 dynamic layout

---
 .../res/values/stylable_message_bubble.xml    |   8 +
 .../timeline/factory/MessageItemFactory.kt    |   5 +
 .../timeline/helper/AvatarSizeProvider.kt     |   5 +-
 .../helper/MessageInformationDataFactory.kt   |  13 +-
 .../detail/timeline/item/AbsMessageItem.kt    |  44 +++--
 .../timeline/item/MessageInformationData.kt   |   5 +-
 .../detail/timeline/item/MessageTextItem.kt   |   2 +-
 .../detail/timeline/view/MessageBubbleView.kt |  58 ++++++
 .../drawable/bg_timeline_incoming_message.xml |   5 +
 .../drawable/bg_timeline_outgoing_message.xml |   5 +
 .../res/layout/item_timeline_event_base.xml   |  60 +------
 ...em_timeline_event_bubble_incoming_base.xml |   8 +
 ...em_timeline_event_bubble_outgoing_base.xml |   8 +
 .../item_timeline_event_text_message_stub.xml |   2 +-
 ...em_timeline_event_view_stubs_container.xml |  61 +++++++
 .../main/res/layout/view_message_bubble.xml   | 170 ++++++++++++++++++
 16 files changed, 368 insertions(+), 91 deletions(-)
 create mode 100644 library/ui-styles/src/main/res/values/stylable_message_bubble.xml
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
 create mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml
 create mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
 create mode 100644 vector/src/main/res/layout/view_message_bubble.xml

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
new file mode 100644
index 0000000000..8da2df33e7
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="MessageBubble">
+        <attr name="incoming_style" format="boolean" />
+    </declare-styleable>
+
+</resources>
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 98deaaf9c3..05973be8be 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -482,6 +482,11 @@ class MessageItemFactory @Inject constructor(
             } else {
                 message(linkifiedBody)
             }
+            if (informationData.sentByMe) {
+                layout(R.layout.item_timeline_event_bubble_outgoing_base)
+            } else {
+                layout(R.layout.item_timeline_event_bubble_incoming_base)
+            }
         }
                 .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
                 .canUseTextFuture(canUseTextFuture)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index 5fc5deb407..c2662e7ae6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -21,10 +21,10 @@ import javax.inject.Inject
 
 class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
 
-    private val avatarStyle = AvatarStyle.SMALL
+    private val avatarStyle = AvatarStyle.X_SMALL
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 8)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
     }
 
     val avatarSize: Int by lazy {
@@ -37,6 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
             BIG(50),
             MEDIUM(40),
             SMALL(30),
+            X_SMALL(24),
             NONE(0)
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 6385494fe1..eef174c0f3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -57,6 +57,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
         val eventId = event.eventId
+        val isSentByMe = event.root.senderId == session.myUserId
+        val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
@@ -65,20 +67,18 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ?: false
 
         val showInformation =
-                addDaySeparator ||
+                (addDaySeparator ||
                         event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
                         event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
                         nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
                         isNextMessageReceivedMoreThanOneHourAgo ||
                         isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()
+                        nextDisplayableEvent.isEdition() ) && !isSentByMe
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
-        val roomSummary = params.partialState.roomSummary
         val e2eDecoration = getE2EDecoration(roomSummary, event)
 
         // SendState Decoration
-        val isSentByMe = event.root.senderId == session.myUserId
         val sendStateDecoration = if (isSentByMe) {
             getSendStateDecoration(
                     event = event,
@@ -97,8 +97,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ageLocalTS = event.root.ageLocalTs,
                 avatarUrl = event.senderInfo.avatarUrl,
                 memberName = event.senderInfo.disambiguatedDisplayName,
-                showInformation = showInformation,
-                forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
+                showAvatar = showInformation,
+                showDisplayName = showInformation,
+                showTimestamp = true,
                 orderedReactionList = event.annotations?.reactionsSummary
                         // ?.filter { isSingleEmoji(it.key) }
                         ?.map {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
index b53495fdaf..a964af6f73 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -22,7 +22,6 @@ import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.TextView
 import androidx.annotation.IdRes
-import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import im.vector.app.R
@@ -63,38 +62,37 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
 
     override fun bind(holder: H) {
         super.bind(holder)
-        if (attributes.informationData.showInformation) {
+        if (attributes.informationData.showAvatar) {
             holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
                 height = attributes.avatarSize
                 width = attributes.avatarSize
             }
-            holder.avatarImageView.visibility = View.VISIBLE
-            holder.avatarImageView.onClick(_avatarClickListener)
-            holder.memberNameView.visibility = View.VISIBLE
-            holder.memberNameView.onClick(_memberNameClickListener)
-            holder.timeView.visibility = View.VISIBLE
-            holder.timeView.text = attributes.informationData.time
-            holder.memberNameView.text = attributes.informationData.memberName
-            holder.memberNameView.setTextColor(attributes.getMemberNameColor())
             attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
             holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
-            holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
+            holder.avatarImageView.isVisible = true
+            holder.avatarImageView.onClick(_avatarClickListener)
         } else {
             holder.avatarImageView.setOnClickListener(null)
-            holder.memberNameView.setOnClickListener(null)
-            holder.avatarImageView.visibility = View.GONE
-            if (attributes.informationData.forceShowTimestamp) {
-                holder.memberNameView.isInvisible = true
-                holder.timeView.isVisible = true
-                holder.timeView.text = attributes.informationData.time
-            } else {
-                holder.memberNameView.isVisible = false
-                holder.timeView.isVisible = false
-            }
             holder.avatarImageView.setOnLongClickListener(null)
-            holder.memberNameView.setOnLongClickListener(null)
+            holder.avatarImageView.isVisible = false
+        }
+        if (attributes.informationData.showDisplayName) {
+            holder.memberNameView.isVisible = true
+            holder.memberNameView.text = attributes.informationData.memberName
+            holder.memberNameView.setTextColor(attributes.getMemberNameColor())
+            holder.memberNameView.onClick(_memberNameClickListener)
+            holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
+        } else {
+            holder.memberNameView.setOnClickListener(null)
+            holder.memberNameView.setOnLongClickListener(null)
+            holder.memberNameView.isVisible = false
+        }
+        if (attributes.informationData.showTimestamp) {
+            holder.timeView.isVisible = true
+            holder.timeView.text = attributes.informationData.time
+        } else {
+            holder.timeView.isVisible = false
         }
-
         // Render send state indicator
         holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
         holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 08aa301538..94c6b32b0a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -31,8 +31,9 @@ data class MessageInformationData(
         val ageLocalTS: Long?,
         val avatarUrl: String?,
         val memberName: CharSequence? = null,
-        val showInformation: Boolean = true,
-        val forceShowTimestamp: Boolean = false,
+        val showAvatar: Boolean,
+        val showDisplayName: Boolean,
+        val showTimestamp: Boolean,
         /*List of reactions (emoji,count,isSelected)*/
         val orderedReactionList: List<ReactionInfoData>? = null,
         val pollResponseAggregatedSummary: PollResponseData? = null,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 747183bce6..d950b7226a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -113,7 +113,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
         previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewType() = STUB_ID + layout
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val messageView by bind<AppCompatTextView>(R.id.messageTextView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
new file mode 100644
index 0000000000..66fb5cd998
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.RelativeLayout
+import androidx.core.content.withStyledAttributes
+import im.vector.app.R
+
+class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
+                                                  defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
+
+    var incoming: Boolean = false
+
+    init {
+        inflate(context, R.layout.view_message_bubble, this)
+        context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
+            incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
+        }
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        val currentLayoutDirection = layoutDirection
+        if (incoming) {
+            findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
+            findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
+            findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message)
+        } else {
+            val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+                View.LAYOUT_DIRECTION_RTL
+            } else {
+                View.LAYOUT_DIRECTION_LTR
+            }
+            findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
+            findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
+            findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message)
+        }
+    }
+}
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
new file mode 100644
index 0000000000..ad4a28a770
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?attr/vctr_system" />
+    <corners android:radius="12dp"/>
+</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
new file mode 100644
index 0000000000..1ab85d2352
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#0F0DBD8B" />
+    <corners android:radius="12dp"/>
+</shape>
\ No newline at end of file
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 3316a651d7..debf578519 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -76,68 +76,16 @@
         android:visibility="gone"
         tools:visibility="visible" />
 
-    <FrameLayout
+    <include
         android:id="@+id/viewStubContainer"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
+        layout="@layout/item_timeline_event_view_stubs_container"
         android:layout_below="@id/messageMemberNameView"
         android:layout_marginEnd="8dp"
         android:layout_toStartOf="@id/messageSendStateImageView"
         android:layout_toEndOf="@id/messageStartGuideline"
-        android:addStatesFromChildren="true">
-
-        <ViewStub
-            android:id="@+id/messageContentTextStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_text_message_stub"
-            tools:visibility="visible" />
-
-        <ViewStub
-            android:id="@+id/messageContentCodeBlockStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_code_block_stub"
-            tools:visibility="visible" />
-
-        <ViewStub
-            android:id="@+id/messageContentMediaStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:inflatedId="@+id/messageContentMedia"
-            android:layout="@layout/item_timeline_event_media_message_stub" />
-
-        <ViewStub
-            android:id="@+id/messageContentFileStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_file_stub" />
-
-        <ViewStub
-            android:id="@+id/messageContentRedactedStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_redacted_stub" />
-
-        <ViewStub
-            android:id="@+id/messagePollStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_poll_stub" />
-
-        <ViewStub
-            android:id="@+id/messageOptionsStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout_height="wrap_content"
-            android:layout="@layout/item_timeline_event_option_buttons_stub" />
-
-        <ViewStub
-            android:id="@+id/messageContentVoiceStub"
-            style="@style/TimelineContentStubBaseParams"
-            android:layout="@layout/item_timeline_event_voice_stub"
-            tools:visibility="visible" />
-
-    </FrameLayout>
+        android:addStatesFromChildren="true"/>
 
     <im.vector.app.core.ui.views.SendStateImageView
         android:id="@+id/messageSendStateImageView"
diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
new file mode 100644
index 0000000000..ac8e3aad48
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:addStatesFromChildren="true"
+    android:background="?attr/selectableItemBackground"
+    app:incoming_style="true" />
diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
new file mode 100644
index 0000000000..42bf1b5f7a
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:addStatesFromChildren="true"
+    android:background="?attr/selectableItemBackground"
+    app:incoming_style="false" />
diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
index 3f9feb93af..4bb612fedf 100644
--- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
@@ -23,6 +23,6 @@
         android:layout_marginBottom="4dp"
         android:foreground="?attr/selectableItemBackground"
         android:visibility="gone"
-        tools:visibility="visible" />
+        tools:visibility="gone" />
 
 </LinearLayout>
diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
new file mode 100644
index 0000000000..04613c665f
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:addStatesFromChildren="true">
+
+    <ViewStub
+        android:id="@+id/messageContentTextStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_text_message_stub"
+        tools:visibility="visible" />
+
+    <ViewStub
+        android:id="@+id/messageContentCodeBlockStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_code_block_stub"
+        tools:visibility="visible" />
+
+    <ViewStub
+        android:id="@+id/messageContentMediaStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:inflatedId="@+id/messageContentMedia"
+        android:layout="@layout/item_timeline_event_media_message_stub" />
+
+    <ViewStub
+        android:id="@+id/messageContentFileStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_file_stub" />
+
+    <ViewStub
+        android:id="@+id/messageContentRedactedStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_redacted_stub" />
+
+    <ViewStub
+        android:id="@+id/messagePollStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_poll_stub" />
+
+    <ViewStub
+        android:id="@+id/messageOptionsStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
+        android:layout="@layout/item_timeline_event_option_buttons_stub" />
+
+    <ViewStub
+        android:id="@+id/messageContentVoiceStub"
+        style="@style/TimelineContentStubBaseParams"
+        android:layout="@layout/item_timeline_event_voice_stub"
+        tools:visibility="visible" />
+
+</FrameLayout>
+
+
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
new file mode 100644
index 0000000000..36c5592f07
--- /dev/null
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge 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:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:parentTag="android.widget.RelativeLayout"
+    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_alignParentEnd="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="4dp"
+        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" />
+
+    <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" />
+
+    <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:addStatesFromChildren="true"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+
+        <RelativeLayout
+            android:id="@+id/bubbleView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginVertical="4dp"
+            android:layout_marginStart="0dp"
+            android:layout_marginEnd="0dp"
+            android:addStatesFromChildren="true"
+            android:background="@drawable/bg_timeline_incoming_message"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            tools:ignore="UselessParent">
+
+            <include
+                android:id="@+id/viewStubContainer"
+                layout="@layout/item_timeline_event_view_stubs_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:addStatesFromChildren="true" />
+
+            <TextView
+                android:id="@+id/messageTimeView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBottom="@id/viewStubContainer"
+                android:layout_marginEnd="8dp"
+                android:layout_marginBottom="4dp"
+                android:layout_toEndOf="@id/viewStubContainer"
+                android:textColor="?vctr_content_tertiary"
+                android:textSize="10sp"
+                tools:text="@tools:sample/date/hhmm" />
+
+        </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="invisible"
+        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>
+
+</merge>
\ No newline at end of file

From bde1df03224ac8cb5422467cb8347da973b687c2 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 5 Jan 2022 11:00:12 +0100
Subject: [PATCH 02/36] Bubbles: continue R&D on UI

---
 .../ui-styles/src/main/res/values/colors.xml  |  4 ++
 .../ui-styles/src/main/res/values/dimens.xml  |  4 ++
 .../res/values/stylable_message_bubble.xml    |  2 +
 .../app/features/home/AvatarRenderer.kt       |  1 +
 .../timeline/helper/AvatarSizeProvider.kt     |  4 +-
 .../detail/timeline/view/MessageBubbleView.kt | 56 ++++++++++++++++++-
 .../main/res/drawable/bg_avatar_border.xml    | 12 ++++
 .../drawable/bg_timeline_incoming_message.xml |  1 -
 .../drawable/bg_timeline_outgoing_message.xml |  1 -
 .../main/res/layout/view_message_bubble.xml   | 43 +++++++++-----
 10 files changed, 109 insertions(+), 19 deletions(-)
 create mode 100644 vector/src/main/res/drawable/bg_avatar_border.xml

diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index 9df2794a1a..e3ec542c89 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -13,6 +13,9 @@
     <color name="button_bot_background_color">#14368BD6</color>
     <color name="button_bot_enabled_text_color">@color/palette_azure</color>
 
+    <color name="bubble_background_outgoing">#0F0DBD8B</color>
+    <color name="bubble_background_incoming">@color/element_system_light</color>
+
     <!-- Notification (do not depends on theme) -->
     <color name="notification_accent_color">@color/palette_azure</color>
     <color name="key_share_req_accent_color">@color/palette_melon</color>
@@ -137,4 +140,5 @@
     <attr name="vctr_presence_indicator_offline" format="color" />
     <color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
     <color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
+
 </resources>
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 9fbf8958da..6eff9295d0 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -47,4 +47,8 @@
     <dimen name="composer_min_height">56dp</dimen>
     <dimen name="composer_attachment_size">52dp</dimen>
     <dimen name="composer_attachment_margin">1dp</dimen>
+
+    <dimen name="chat_bubble_margin_start">28dp</dimen>
+    <dimen name="chat_bubble_margin_end">62dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 8da2df33e7..1f55d07486 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,6 +3,8 @@
 
     <declare-styleable name="MessageBubble">
         <attr name="incoming_style" format="boolean" />
+        <attr name="is_first" format="boolean" />
+        <attr name="is_last" format="boolean" />
     </declare-styleable>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index 2ee3233637..be689eb27a 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -145,6 +145,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
                         }
                         else                    -> {
                             it.apply(RequestOptions.circleCropTransform())
+
                         }
                     }
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index c2662e7ae6..00b02c2cf0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -24,7 +24,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
     private val avatarStyle = AvatarStyle.X_SMALL
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
     }
 
     val avatarSize: Int by lazy {
@@ -37,7 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
             BIG(50),
             MEDIUM(40),
             SMALL(30),
-            X_SMALL(24),
+            X_SMALL(28),
             NONE(0)
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 66fb5cd998..9ad7eb097c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -17,42 +17,94 @@
 package im.vector.app.features.home.room.detail.timeline.view
 
 import android.content.Context
+import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
+import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
+import androidx.core.view.updateLayoutParams
+import com.google.android.material.shape.CornerFamily
+import com.google.android.material.shape.MaterialShapeDrawable
+import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
+import im.vector.app.core.utils.DimensionConverter
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
 
     var incoming: Boolean = false
+    var isFirst: Boolean = false
+    var isLast: Boolean = false
+    var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
 
     init {
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
+            isFirst = getBoolean(R.styleable.MessageBubble_is_first, false)
+            isLast = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
     }
 
     override fun onFinishInflate() {
         super.onFinishInflate()
         val currentLayoutDirection = layoutDirection
+        findViewById<ViewGroup>(R.id.bubbleView).apply {
+            background = createBackgroundDrawable()
+            outlineProvider = ViewOutlineProvider.BACKGROUND
+            clipToOutline = true
+        }
         if (incoming) {
             findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
             findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
             findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
-            findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message)
+            findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
         } else {
             val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
             } else {
                 View.LAYOUT_DIRECTION_LTR
             }
+
             findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
             findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
             findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
-            findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message)
+            findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
         }
     }
+
+    private fun createBackgroundDrawable(): Drawable {
+        val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT
+        val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT
+        val topRadius = if (isFirst) cornerRadius else 0f
+        val bottomRadius = if (isLast) cornerRadius else 0f
+        val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
+        val backgroundColor: Int
+        if (incoming) {
+            backgroundColor = R.color.bubble_background_incoming
+            shapeAppearanceModelBuilder
+                    .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setTopLeftCorner(topCornerFamily, topRadius)
+                    .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
+        } else {
+            backgroundColor = R.color.bubble_background_outgoing
+            shapeAppearanceModelBuilder
+                    .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setTopRightCorner(topCornerFamily, topRadius)
+                    .setBottomRightCorner(bottomCornerFamily, bottomRadius)
+        }
+        val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
+        val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
+        shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        return shapeDrawable
+    }
 }
diff --git a/vector/src/main/res/drawable/bg_avatar_border.xml b/vector/src/main/res/drawable/bg_avatar_border.xml
new file mode 100644
index 0000000000..e22731c1a3
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_avatar_border.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+
+    <solid android:color="@android:color/transparent"/>
+
+    <stroke
+        android:width="2dp"
+        android:color="?android:colorBackground"/>
+
+</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
index ad4a28a770..2cbca33702 100644
--- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
+++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="?attr/vctr_system" />
-    <corners android:radius="12dp"/>
 </shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
index 1ab85d2352..0f75705a77 100644
--- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
+++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="#0F0DBD8B" />
-    <corners android:radius="12dp"/>
 </shape>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 36c5592f07..12f16910f1 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -19,9 +19,12 @@
         android:id="@+id/messageAvatarImageView"
         android:layout_width="44dp"
         android:layout_height="44dp"
-        android:layout_marginStart="8dp"
+        android:layout_marginStart="12dp"
         android:layout_marginTop="4dp"
+        android:padding="2dp"
+        android:background="@drawable/bg_avatar_border"
         android:contentDescription="@string/avatar"
+        android:elevation="2dp"
         tools:src="@sample/user_round_avatars" />
 
     <TextView
@@ -31,7 +34,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_alignParentEnd="true"
-        android:layout_marginStart="8dp"
+        android:layout_marginStart="12dp"
         android:layout_marginEnd="4dp"
         android:layout_toEndOf="@id/messageStartGuideline"
         android:ellipsize="end"
@@ -46,6 +49,13 @@
         android:layout_height="0dp"
         tools:layout_marginStart="52dp" />
 
+    <View
+        android:id="@+id/messageEndGuideline"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="64dp" />
+
     <Space
         android:id="@+id/decorationSpace"
         android:layout_width="4dp"
@@ -59,6 +69,7 @@
         android:layout_alignTop="@id/bubbleWrapper"
         android:layout_alignEnd="@id/decorationSpace"
         android:layout_marginTop="7dp"
+        android:elevation="2dp"
         android:visibility="gone"
         tools:visibility="visible" />
 
@@ -67,45 +78,49 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/messageMemberNameView"
-        android:layout_toStartOf="@id/messageSendStateImageView"
+        android:layout_toStartOf="@id/messageEndGuideline"
         android:layout_toEndOf="@id/messageStartGuideline"
         android:addStatesFromChildren="true"
         android:clipChildren="false"
         android:clipToPadding="false">
 
-        <RelativeLayout
+        <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/bubbleView"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginVertical="4dp"
             android:layout_marginStart="0dp"
             android:layout_marginEnd="0dp"
             android:addStatesFromChildren="true"
             android:background="@drawable/bg_timeline_incoming_message"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            tools:ignore="UselessParent">
+            android:paddingStart="4dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
 
             <include
                 android:id="@+id/viewStubContainer"
                 layout="@layout/item_timeline_event_view_stubs_container"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:addStatesFromChildren="true" />
+                android:addStatesFromChildren="true"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintEnd_toStartOf="@id/messageTimeView"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintWidth_max="300dp" />
 
             <TextView
                 android:id="@+id/messageTimeView"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_alignBottom="@id/viewStubContainer"
                 android:layout_marginEnd="8dp"
                 android:layout_marginBottom="4dp"
-                android:layout_toEndOf="@id/viewStubContainer"
                 android:textColor="?vctr_content_tertiary"
                 android:textSize="10sp"
+                app:layout_constraintBottom_toBottomOf="@id/viewStubContainer"
+                app:layout_constraintEnd_toEndOf="parent"
                 tools:text="@tools:sample/date/hhmm" />
 
-        </RelativeLayout>
+        </androidx.constraintlayout.widget.ConstraintLayout>
     </FrameLayout>
 
     <im.vector.app.core.ui.views.SendStateImageView
@@ -143,6 +158,9 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/bubbleWrapper"
+        android:layout_marginTop="2dp"
+        android:layout_marginBottom="2dp"
+        android:layout_toStartOf="@id/messageEndGuideline"
         android:layout_toEndOf="@id/messageStartGuideline"
         android:addStatesFromChildren="true"
         android:orientation="vertical">
@@ -151,7 +169,6 @@
             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"

From ad63d3de1c57cb8d9bf83741c7c59771fb7fc514 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 6 Jan 2022 19:07:28 +0100
Subject: [PATCH 03/36] Bubbles: still R&D. Not sure how to handle every event
 types.

---
 .../res/values/stylable_message_bubble.xml    |  1 +
 .../timeline/TimelineEventController.kt       |  8 +-
 .../timeline/factory/MessageItemFactory.kt    |  7 ++
 .../helper/MessageInformationDataFactory.kt   |  5 ++
 .../timeline/item/AbsBaseMessageItem.kt       |  5 ++
 .../timeline/item/MessageImageVideoItem.kt    |  3 +
 .../timeline/item/MessageInformationData.kt   |  4 +-
 .../detail/timeline/view/MessageBubbleView.kt | 86 +++++++++++++++----
 .../timeline/view/MessageViewConfiguration.kt | 24 ++++++
 ...item_timeline_event_media_message_stub.xml |  1 +
 .../main/res/layout/view_message_bubble.xml   |  2 +-
 11 files changed, 121 insertions(+), 25 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 1f55d07486..32ed23c613 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,6 +3,7 @@
 
     <declare-styleable name="MessageBubble">
         <attr name="incoming_style" format="boolean" />
+        <attr name="show_background" format="boolean" />
         <attr name="is_first" format="boolean" />
         <attr name="is_last" format="boolean" />
     </declare-styleable>
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 241ccb7428..dc5c76725b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -355,12 +355,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
         (0 until modelCache.size).forEach { position ->
             val event = currentSnapshot[position]
             val nextEvent = currentSnapshot.nextOrNull(position)
-            val prevEvent = currentSnapshot.prevOrNull(position)
-            val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
-                timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
-            }
             // Should be build if not cached or if model should be refreshed
             if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
+                val prevEvent = currentSnapshot.prevOrNull(position)
+                val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
+                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
+                }
                 val timelineEventsGroup = timelineEventsGroups.getOrNull(event)
                 val params = TimelineItemFactoryParams(
                         event = event,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 697c307d06..c42d50e924 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -389,6 +389,13 @@ class MessageItemFactory @Inject constructor(
                 allowNonMxcUrls = informationData.sendState.isSending()
         )
         return MessageImageVideoItem_()
+                .layout(
+                        if (informationData.sentByMe) {
+                            R.layout.item_timeline_event_bubble_outgoing_base
+                        } else {
+                            R.layout.item_timeline_event_bubble_incoming_base
+                        }
+                )
                 .attributes(attributes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .imageContentRenderer(imageContentRenderer)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index b203c23978..b9ef9ca558 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -57,8 +57,11 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
+        val prevEvent = params.prevEvent
         val eventId = event.eventId
         val isSentByMe = event.root.senderId == session.myUserId
+        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId
+        val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId
         val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
@@ -128,6 +131,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                     ReferencesInfoData(verificationState)
                 },
                 sentByMe = isSentByMe,
+                isFirstFromThisSender = isFirstFromThisSender,
+                isLastFromThisSender = isLastFromThisSender,
                 e2eDecoration = e2eDecoration,
                 sendStateDecoration = sendStateDecoration
         )
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 080b766258..3d9db4e827 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView
 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.view.MessageViewConfiguration
 import im.vector.app.features.reactions.widget.ReactionButton
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -98,6 +99,10 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
 
         holder.view.onClick(baseAttributes.itemClickListener)
         holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
+        (holder.view as? MessageViewConfiguration)?.apply {
+            isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender
+            isLastFromSender = baseAttributes.informationData.isLastFromThisSender
+        }
     }
 
     override fun unbind(holder: H) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 3ae91db97c..8e42297bc1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,6 +29,8 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
+import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView
+import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -70,6 +72,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
+        (holder.view as? MessageViewConfiguration)?.displayBorder = false
     }
 
     override fun unbind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index b105bfd6be..76fc9a5eff 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -42,7 +42,9 @@ data class MessageInformationData(
         val referencesInfoData: ReferencesInfoData? = null,
         val sentByMe: Boolean,
         val e2eDecoration: E2EDecoration = E2EDecoration.NONE,
-        val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE
+        val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE,
+        val isFirstFromThisSender: Boolean = false,
+        val isLastFromThisSender: Boolean = false
 ) : Parcelable {
 
     val matrixItem: MatrixItem
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 9ad7eb097c..937d78ebb6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -20,9 +20,10 @@ import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
-import android.view.ViewGroup
 import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
 import androidx.core.view.updateLayoutParams
@@ -33,34 +34,61 @@ import im.vector.app.R
 import im.vector.app.core.utils.DimensionConverter
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                                  defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
+                                                  defStyleAttr: Int = 0)
+    : RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
 
-    var incoming: Boolean = false
-    var isFirst: Boolean = false
-    var isLast: Boolean = false
-    var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
+    override var isIncoming: Boolean = false
+        set(value) {
+            field = value
+            render()
+        }
+
+    override var isFirstFromSender: Boolean = false
+        set(value) {
+            field = value
+            render()
+        }
+    override var isLastFromSender: Boolean = false
+        set(value) {
+            field = value
+            render()
+        }
+
+    override var displayBorder: Boolean = true
+        set(value) {
+            field = value
+            render()
+        }
+
+    private val cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
 
     init {
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
-            incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
-            isFirst = getBoolean(R.styleable.MessageBubble_is_first, false)
-            isLast = getBoolean(R.styleable.MessageBubble_is_last, false)
+            isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
+            displayBorder = getBoolean(R.styleable.MessageBubble_show_background, true)
+            isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
+            isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
     }
 
     override fun onFinishInflate() {
         super.onFinishInflate()
+        render()
+    }
+
+    private fun render() {
         val currentLayoutDirection = layoutDirection
-        findViewById<ViewGroup>(R.id.bubbleView).apply {
+        val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView)
+        bubbleView.apply {
             background = createBackgroundDrawable()
             outlineProvider = ViewOutlineProvider.BACKGROUND
             clipToOutline = true
         }
-        if (incoming) {
+        if (isIncoming) {
             findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
             findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
-            findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            bubbleView.layoutDirection = currentLayoutDirection
             findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
             }
@@ -73,21 +101,37 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
 
             findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
             findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
-            findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            bubbleView.layoutDirection = currentLayoutDirection
             findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
             }
         }
+        ConstraintSet().apply {
+            clone(bubbleView)
+            clear(R.id.viewStubContainer, ConstraintSet.END)
+            if (displayBorder) {
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
+            } else {
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
+            }
+            applyTo(bubbleView)
+        }
     }
 
     private fun createBackgroundDrawable(): Drawable {
-        val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT
-        val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT
-        val topRadius = if (isFirst) cornerRadius else 0f
-        val bottomRadius = if (isLast) cornerRadius else 0f
+        val (topCornerFamily, topRadius) = if (isFirstFromSender) {
+            Pair(CornerFamily.ROUNDED, cornerRadius)
+        } else {
+            Pair(CornerFamily.CUT, 0f)
+        }
+        val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) {
+            Pair(CornerFamily.ROUNDED, cornerRadius)
+        } else {
+            Pair(CornerFamily.CUT, 0f)
+        }
         val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
         val backgroundColor: Int
-        if (incoming) {
+        if (isIncoming) {
             backgroundColor = R.color.bubble_background_incoming
             shapeAppearanceModelBuilder
                     .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
@@ -104,7 +148,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
         val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
-        shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        if (displayBorder) {
+            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        } else {
+            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, android.R.color.transparent)
+        }
         return shapeDrawable
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
new file mode 100644
index 0000000000..f441005edf
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.view
+
+interface MessageViewConfiguration {
+    var isIncoming: Boolean
+    var isFirstFromSender: Boolean
+    var isLastFromSender: Boolean
+    var displayBorder: Boolean
+}
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index 9e2a5ef3ed..988e2d8bb1 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -4,6 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@color/palette_element_green"
     tools:viewBindingIgnore="true">
 
     <ImageView
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 12f16910f1..8e9a95222a 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -103,7 +103,7 @@
                 android:layout_height="wrap_content"
                 android:addStatesFromChildren="true"
                 app:layout_constrainedWidth="true"
-                app:layout_constraintEnd_toStartOf="@id/messageTimeView"
+                app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
                 app:layout_constraintWidth_max="300dp" />

From f7c9b36cef33a037c0237f0c37f817ccdb542042 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 11 Jan 2022 11:57:35 +0100
Subject: [PATCH 04/36] Bubbles: continue exploration

---
 .../main/res/values/stylable_message_bubble.xml   |  2 +-
 .../detail/timeline/TimelineEventController.kt    |  4 ++++
 .../timeline/factory/TimelineItemFactoryParams.kt |  1 +
 .../helper/MessageInformationDataFactory.kt       | 10 ++++++----
 .../detail/timeline/item/MessageImageVideoItem.kt |  3 +--
 .../detail/timeline/view/MessageBubbleView.kt     | 15 ++++++---------
 .../timeline/view/MessageViewConfiguration.kt     |  2 +-
 .../item_timeline_event_media_message_stub.xml    |  2 --
 8 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 32ed23c613..f7a877e3ed 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,7 +3,7 @@
 
     <declare-styleable name="MessageBubble">
         <attr name="incoming_style" format="boolean" />
-        <attr name="show_background" format="boolean" />
+        <attr name="show_time_overlay" format="boolean" />
         <attr name="is_first" format="boolean" />
         <attr name="is_last" format="boolean" />
     </declare-styleable>
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index dc5c76725b..460c332812 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -358,6 +358,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             // Should be build if not cached or if model should be refreshed
             if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
                 val prevEvent = currentSnapshot.prevOrNull(position)
+                val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull {
+                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
+                }
                 val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
                     timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
                 }
@@ -365,6 +368,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 val params = TimelineItemFactoryParams(
                         event = event,
                         prevEvent = prevEvent,
+                        prevDisplayableEvent = prevDisplayableEvent,
                         nextEvent = nextEvent,
                         nextDisplayableEvent = nextDisplayableEvent,
                         partialState = partialState,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
index cdfedb2925..fad558344c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 data class TimelineItemFactoryParams(
         val event: TimelineEvent,
         val prevEvent: TimelineEvent? = null,
+        val prevDisplayableEvent: TimelineEvent? = null,
         val nextEvent: TimelineEvent? = null,
         val nextDisplayableEvent: TimelineEvent? = null,
         val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index b9ef9ca558..b9ea78e0db 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -57,19 +57,21 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
-        val prevEvent = params.prevEvent
+        val prevDisplayableEvent = params.prevDisplayableEvent
         val eventId = event.eventId
         val isSentByMe = event.root.senderId == session.myUserId
-        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId
-        val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId
         val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
+
         val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
                 ?: false
 
+        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+
         val showInformation =
                 (addDaySeparator ||
                         event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
@@ -77,7 +79,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                         nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
                         isNextMessageReceivedMoreThanOneHourAgo ||
                         isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition() ) && !isSentByMe
+                        nextDisplayableEvent.isEdition()) && !isSentByMe
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 8e42297bc1..e865354747 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,7 +29,6 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
-import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView
 import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
@@ -72,7 +71,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
-        (holder.view as? MessageViewConfiguration)?.displayBorder = false
+        (holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = false
     }
 
     override fun unbind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 937d78ebb6..5bb732bdde 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -54,7 +54,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             render()
         }
 
-    override var displayBorder: Boolean = true
+    override var showTimeAsOverlay: Boolean = true
         set(value) {
             field = value
             render()
@@ -66,7 +66,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
-            displayBorder = getBoolean(R.styleable.MessageBubble_show_background, true)
+            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, true)
             isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
             isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
@@ -109,13 +109,14 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         ConstraintSet().apply {
             clone(bubbleView)
             clear(R.id.viewStubContainer, ConstraintSet.END)
-            if (displayBorder) {
+            if (showTimeAsOverlay) {
                 connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
             } else {
                 connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
             }
             applyTo(bubbleView)
         }
+
     }
 
     private fun createBackgroundDrawable(): Drawable {
@@ -147,12 +148,8 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
                     .setBottomRightCorner(bottomCornerFamily, bottomRadius)
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
-        val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
-        if (displayBorder) {
-            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
-        } else {
-            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, android.R.color.transparent)
+        return MaterialShapeDrawable(shapeAppearanceModel).apply {
+            fillColor = ContextCompat.getColorStateList(context, backgroundColor)
         }
-        return shapeDrawable
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
index f441005edf..bfb5884674 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
@@ -20,5 +20,5 @@ interface MessageViewConfiguration {
     var isIncoming: Boolean
     var isFirstFromSender: Boolean
     var isLastFromSender: Boolean
-    var displayBorder: Boolean
+    var showTimeAsOverlay: Boolean
 }
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index 988e2d8bb1..4fe37830c2 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -4,14 +4,12 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@color/palette_element_green"
     tools:viewBindingIgnore="true">
 
     <ImageView
         android:id="@+id/messageThumbnailView"
         android:layout_width="375dp"
         android:layout_height="0dp"
-        android:layout_marginEnd="32dp"
         android:contentDescription="@string/a11y_image"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0"

From af542a8243ff562791f8886cb438159bd314a253 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 11 Jan 2022 15:38:58 +0100
Subject: [PATCH 05/36] Bubbles: start adding "theming" mechanism

---
 .../timeline/factory/EncryptedItemFactory.kt  |  1 +
 .../timeline/factory/MessageItemFactory.kt    | 19 +---
 .../timeline/helper/AvatarSizeProvider.kt     | 26 ++++--
 .../helper/MessageInformationDataFactory.kt   | 24 ++---
 .../detail/timeline/item/AbsMessageItem.kt    |  6 +-
 .../timeline/item/MessageInformationData.kt   |  5 +-
 .../timeline/style/TimelineLayoutSettings.kt  | 22 +++++
 .../style/TimelineLayoutSettingsProvider.kt   | 26 ++++++
 .../timeline/style/TimelineMessageLayout.kt   | 44 ++++++++++
 .../style/TimelineMessageLayoutFactory.kt     | 88 +++++++++++++++++++
 10 files changed, 212 insertions(+), 49 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index b8d7d96ecf..5112604dd3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -108,6 +108,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
                 val informationData = messageInformationDataFactory.create(params)
                 val attributes = attributesFactory.create(event.root.content.toModel<EncryptedEventContent>(), informationData, params.callback)
                 return MessageTextItem_()
+                        .layout(informationData.messageLayout.layoutRes)
                         .leftGuideline(avatarSizeProvider.leftGuideline)
                         .highlighted(params.isHighlighted)
                         .attributes(attributes)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index c42d50e924..33d77fe16c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -154,7 +154,7 @@ class MessageItemFactory @Inject constructor(
 
 //        val all = event.root.toContent()
 //        val ev = all.toModel<Event>()
-        return when (messageContent) {
+        val messageItem = when (messageContent) {
             is MessageEmoteContent               -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
             is MessageTextContent                -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
             is MessageImageInfoContent           -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
@@ -172,6 +172,9 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
+        return messageItem?.apply {
+            layout(informationData.messageLayout.layoutRes)
+        }
     }
 
     private fun buildPollContent(pollContent: MessagePollContent,
@@ -389,13 +392,6 @@ class MessageItemFactory @Inject constructor(
                 allowNonMxcUrls = informationData.sendState.isSending()
         )
         return MessageImageVideoItem_()
-                .layout(
-                        if (informationData.sentByMe) {
-                            R.layout.item_timeline_event_bubble_outgoing_base
-                        } else {
-                            R.layout.item_timeline_event_bubble_incoming_base
-                        }
-                )
                 .attributes(attributes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .imageContentRenderer(imageContentRenderer)
@@ -517,13 +513,6 @@ class MessageItemFactory @Inject constructor(
                             linkifiedBody
                         }.toEpoxyCharSequence()
                 )
-                .layout(
-                        if (informationData.sentByMe) {
-                            R.layout.item_timeline_event_bubble_outgoing_base
-                        } else {
-                            R.layout.item_timeline_event_bubble_incoming_base
-                        }
-                )
                 .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
                 .bindingOptions(bindingOptions)
                 .searchForPills(isFormatted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index 00b02c2cf0..a34c216fad 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -17,14 +17,22 @@
 package im.vector.app.features.home.room.detail.timeline.helper
 
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettings
+import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettingsProvider
 import javax.inject.Inject
 
-class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
+class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter,
+                                             private val layoutSettingsProvider: TimelineLayoutSettingsProvider) {
 
-    private val avatarStyle = AvatarStyle.X_SMALL
+    private val avatarStyle by lazy {
+        when (layoutSettingsProvider.getLayoutSettings()) {
+            TimelineLayoutSettings.MODERN -> AvatarStyle.SMALL
+            TimelineLayoutSettings.BUBBLE -> AvatarStyle.BUBBLE
+        }
+    }
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + avatarStyle.marginDP)
     }
 
     val avatarSize: Int by lazy {
@@ -33,12 +41,12 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
 
     companion object {
 
-        enum class AvatarStyle(val avatarSizeDP: Int) {
-            BIG(50),
-            MEDIUM(40),
-            SMALL(30),
-            X_SMALL(28),
-            NONE(0)
+        enum class AvatarStyle(val avatarSizeDP: Int, val marginDP: Int) {
+            BIG(50, 8),
+            MEDIUM(40, 8),
+            SMALL(30, 8),
+            BUBBLE(28, 4),
+            NONE(0, 8)
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index b9ea78e0db..2edab8af74 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -27,7 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
 import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
 import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
 import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
-import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
 import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
@@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
-import org.matrix.android.sdk.api.session.room.timeline.isEdition
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import javax.inject.Inject
 
@@ -51,8 +50,7 @@ import javax.inject.Inject
  */
 class MessageInformationDataFactory @Inject constructor(private val session: Session,
                                                         private val dateFormatter: VectorDateFormatter,
-                                                        private val visibilityHelper: TimelineEventVisibilityHelper,
-                                                        private val vectorPreferences: VectorPreferences) {
+                                                        private val messageLayoutFactory: TimelineMessageLayoutFactory) {
 
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
@@ -66,21 +64,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
 
-        val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
-                ?: false
-
         val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
         val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
-        val showInformation =
-                (addDaySeparator ||
-                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
-                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
-                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
-                        isNextMessageReceivedMoreThanOneHourAgo ||
-                        isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()) && !isSentByMe
-
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
 
@@ -95,6 +81,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
             SendStateDecoration.NONE
         }
 
+        val messageLayout = messageLayoutFactory.create(params)
+
         return MessageInformationData(
                 eventId = eventId,
                 senderId = event.root.senderId ?: "",
@@ -103,9 +91,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ageLocalTS = event.root.ageLocalTs,
                 avatarUrl = event.senderInfo.avatarUrl,
                 memberName = event.senderInfo.disambiguatedDisplayName,
-                showAvatar = showInformation,
-                showDisplayName = showInformation,
-                showTimestamp = true,
+                messageLayout = messageLayout,
                 orderedReactionList = event.annotations?.reactionsSummary
                         // ?.filter { isSingleEmoji(it.key) }
                         ?.map {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
index a964af6f73..9f3b2bddf2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -62,7 +62,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
 
     override fun bind(holder: H) {
         super.bind(holder)
-        if (attributes.informationData.showAvatar) {
+        if (attributes.informationData.messageLayout.showAvatar) {
             holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
                 height = attributes.avatarSize
                 width = attributes.avatarSize
@@ -76,7 +76,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
             holder.avatarImageView.setOnLongClickListener(null)
             holder.avatarImageView.isVisible = false
         }
-        if (attributes.informationData.showDisplayName) {
+        if (attributes.informationData.messageLayout.showDisplayName) {
             holder.memberNameView.isVisible = true
             holder.memberNameView.text = attributes.informationData.memberName
             holder.memberNameView.setTextColor(attributes.getMemberNameColor())
@@ -87,7 +87,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
             holder.memberNameView.setOnLongClickListener(null)
             holder.memberNameView.isVisible = false
         }
-        if (attributes.informationData.showTimestamp) {
+        if (attributes.informationData.messageLayout.showTimestamp) {
             holder.timeView.isVisible = true
             holder.timeView.text = attributes.informationData.time
         } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 76fc9a5eff..629d20e898 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.home.room.detail.timeline.item
 
 import android.os.Parcelable
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -31,9 +32,7 @@ data class MessageInformationData(
         val ageLocalTS: Long?,
         val avatarUrl: String?,
         val memberName: CharSequence? = null,
-        val showAvatar: Boolean,
-        val showDisplayName: Boolean,
-        val showTimestamp: Boolean,
+        val messageLayout: TimelineMessageLayout,
         /*List of reactions (emoji,count,isSelected)*/
         val orderedReactionList: List<ReactionInfoData>? = null,
         val pollResponseAggregatedSummary: PollResponseData? = null,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
new file mode 100644
index 0000000000..873ef8c907
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+enum class TimelineLayoutSettings {
+    MODERN,
+    BUBBLE
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
new file mode 100644
index 0000000000..a10c95befe
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+import javax.inject.Inject
+
+class TimelineLayoutSettingsProvider @Inject constructor() {
+
+    fun getLayoutSettings(): TimelineLayoutSettings {
+        return TimelineLayoutSettings.BUBBLE
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
new file mode 100644
index 0000000000..48dba0a58a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+import android.os.Parcelable
+import im.vector.app.R
+import kotlinx.parcelize.Parcelize
+
+sealed interface TimelineMessageLayout : Parcelable {
+    val layoutRes: Int
+    val showAvatar: Boolean
+    val showDisplayName: Boolean
+    val showTimestamp: Boolean
+
+    @Parcelize
+    data class Modern(override val showAvatar: Boolean,
+                      override val showDisplayName: Boolean,
+                      override val showTimestamp: Boolean,
+                      override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout
+
+    @Parcelize
+    data class Bubble(override val showAvatar: Boolean,
+                      override val showDisplayName: Boolean,
+                      override val showTimestamp: Boolean = true,
+                      val isIncoming: Boolean,
+                      val isFirstFromThisSender: Boolean,
+                      val isLastFromThisSender: Boolean,
+                      override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base,
+    ) : TimelineMessageLayout
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
new file mode 100644
index 0000000000..18df8c133b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+import im.vector.app.core.extensions.localDateTime
+import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
+import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
+import org.matrix.android.sdk.api.session.room.timeline.isEdition
+import javax.inject.Inject
+
+class TimelineMessageLayoutFactory @Inject constructor(private val session: Session,
+                                                       private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
+                                                       private val vectorPreferences: VectorPreferences) {
+
+    fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
+
+        val event = params.event
+        val nextDisplayableEvent = params.nextDisplayableEvent
+        val prevDisplayableEvent = params.prevDisplayableEvent
+        val isSentByMe = event.root.senderId == session.myUserId
+
+        val date = event.root.localDateTime()
+        val nextDate = nextDisplayableEvent?.root?.localDateTime()
+        val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
+
+        val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
+                ?: false
+
+        val showInformation =
+                (addDaySeparator ||
+                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
+                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
+                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
+                        isNextMessageReceivedMoreThanOneHourAgo ||
+                        isTileTypeMessage(nextDisplayableEvent) ||
+                        nextDisplayableEvent.isEdition()) && !isSentByMe
+
+        val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
+            TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps())
+            TimelineLayoutSettings.BUBBLE -> {
+                val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                TimelineMessageLayout.Bubble(
+                        showAvatar = showInformation,
+                        showDisplayName = showInformation,
+                        isIncoming = !isSentByMe,
+                        isFirstFromThisSender = isFirstFromThisSender,
+                        isLastFromThisSender = isLastFromThisSender
+                )
+            }
+        }
+        return messageLayout
+    }
+
+    /**
+     * Tiles type message never show the sender information (like verification request), so we should repeat it for next message
+     * even if same sender
+     */
+    private fun isTileTypeMessage(event: TimelineEvent?): Boolean {
+        return when (event?.root?.getClearType()) {
+            EventType.KEY_VERIFICATION_DONE,
+            EventType.KEY_VERIFICATION_CANCEL -> true
+            EventType.MESSAGE                 -> {
+                event.getLastMessageContent() is MessageVerificationRequestContent
+            }
+            else                              -> false
+        }
+    }
+}

From f7df0b891efbf53d55f46f574d0da9a4de774a08 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 12 Jan 2022 18:45:40 +0100
Subject: [PATCH 06/36] Bubbles: fix recycling issue

---
 .../detail/timeline/factory/MessageItemFactory.kt |  4 +++-
 .../room/detail/timeline/item/BaseEventItem.kt    | 15 +++++++++++++++
 .../detail/timeline/item/CallTileTimelineItem.kt  |  2 +-
 .../home/room/detail/timeline/item/DefaultItem.kt |  2 +-
 .../timeline/item/MergedMembershipEventsItem.kt   |  2 +-
 .../timeline/item/MergedRoomCreationItem.kt       |  2 +-
 .../detail/timeline/item/MessageBlockCodeItem.kt  |  2 +-
 .../room/detail/timeline/item/MessageFileItem.kt  |  2 +-
 .../detail/timeline/item/MessageImageVideoItem.kt |  2 +-
 .../room/detail/timeline/item/MessageTextItem.kt  |  2 +-
 .../room/detail/timeline/item/MessageVoiceItem.kt |  2 +-
 .../home/room/detail/timeline/item/NoticeItem.kt  |  2 +-
 .../home/room/detail/timeline/item/PollItem.kt    |  2 ++
 .../detail/timeline/item/RedactedMessageItem.kt   |  2 +-
 .../timeline/item/StatusTileTimelineItem.kt       |  2 +-
 .../timeline/item/VerificationRequestItem.kt      |  2 +-
 .../timeline/item/WidgetTileTimelineItem.kt       |  2 +-
 17 files changed, 34 insertions(+), 15 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 33d77fe16c..80343fd909 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -172,7 +172,9 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
-        return messageItem?.apply {
+        return messageItem?.takeIf {
+            it.layout == R.layout.item_timeline_event_base
+        }?.apply {
             layout(informationData.messageLayout.layoutRes)
         }
     }
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 5dfbf5d8f6..3df6393c05 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
@@ -27,6 +27,7 @@ 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.utils.DimensionConverter
+import timber.log.Timber
 
 /**
  * Children must override getViewType()
@@ -43,6 +44,20 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
     lateinit var dimensionConverter: DimensionConverter
 
+    final override fun getViewType(): Int {
+        // This makes sure we have a unique integer for the combination of layout and ViewStubId.
+        return pairingFunction(layout, getViewStubId()).also {
+            Timber.v("GetViewType: for ${javaClass.canonicalName} $it with layout:$layout and stubId:${getViewStubId()}")
+        }
+    }
+
+    abstract fun getViewStubId(): Int
+
+    // Szudzik function
+    private fun pairingFunction(a: Int, b: Int): Int {
+        return if (a >= b) a * a + a + b else a + b * b
+    }
+
     @CallSuper
     override fun bind(holder: H) {
         super.bind(holder)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
index 5f8ac822da..218d648318 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
@@ -50,7 +50,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
     @EpoxyAttribute
     lateinit var attributes: Attributes
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     override fun bind(holder: Holder) {
         super.bind(holder)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt
index 9d0a3b9a9c..9d437754d0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt
@@ -46,7 +46,7 @@ abstract class DefaultItem : BaseEventItem<DefaultItem.Holder>() {
         return listOf(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : BaseHolder(STUB_ID) {
         val avatarImageView by bind<ImageView>(R.id.itemDefaultAvatarView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
index a52ddf8336..e19dc33fff 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
@@ -29,7 +29,7 @@ import im.vector.app.features.home.AvatarRenderer
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
 abstract class MergedMembershipEventsItem : BasedMergedItem<MergedMembershipEventsItem.Holder>() {
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     @EpoxyAttribute
     override lateinit var attributes: Attributes
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
index 1e8e96426f..9f631f7a0e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
@@ -51,7 +51,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
     var movementMethod: MovementMethod? = null
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     override fun bind(holder: Holder) {
         super.bind(holder)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
index be9b727017..9e162a8f1e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
@@ -44,7 +44,7 @@ abstract class MessageBlockCodeItem : AbsMessageItem<MessageBlockCodeItem.Holder
         holder.editedView.setTextOrHide(editedSpan?.charSequence)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val messageView by bind<TextView>(R.id.codeBlockTextView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
index b15f909b79..bd35532eae 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
@@ -95,7 +95,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
         contentDownloadStateTrackerBinder.unbind(mxcUrl)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index e865354747..e20ad48b17 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -83,7 +83,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         super.unbind(holder)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val progressLayout by bind<ViewGroup>(R.id.messageMediaUploadProgressLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 6a438d3a06..e83611d6d8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -115,7 +115,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
         previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater)
     }
 
-    override fun getViewType() = STUB_ID + layout
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val messageView by bind<AppCompatTextView>(R.id.messageTextView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
index f006c2aa35..b6fa684eca 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
@@ -120,7 +120,7 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
         voiceMessagePlaybackTracker.unTrack(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val voiceLayout by bind<ViewGroup>(R.id.voiceLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
index 2851668df5..e998b7e58f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
@@ -64,7 +64,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
         return listOf(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : BaseHolder(STUB_ID) {
         val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
index 1308fa49c8..784eec87fe 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
@@ -46,6 +46,8 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
     @EpoxyAttribute
     lateinit var optionViewStates: List<PollOptionViewState>
 
+    override fun getViewStubId() = STUB_ID
+
     override fun bind(holder: Holder) {
         super.bind(holder)
         val relatedEventId = eventId ?: return
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
index 282550daec..204bab2254 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
@@ -22,7 +22,7 @@ import im.vector.app.R
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>() {
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     override fun shouldShowReactionAtBottom() = false
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
index 6531efb82d..66bf41ddac 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
@@ -40,7 +40,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem<StatusTileTimelineIte
     @EpoxyAttribute
     lateinit var attributes: Attributes
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     @SuppressLint("SetTextI18n")
     override fun bind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
index f4e093a428..1c1b064bf5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
@@ -51,7 +51,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
     @EpoxyAttribute
     var callback: TimelineEventController.Callback? = null
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     @SuppressLint("SetTextI18n")
     override fun bind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt
index 07b55c8ec2..151a044b2e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt
@@ -41,7 +41,7 @@ abstract class WidgetTileTimelineItem : AbsBaseMessageItem<WidgetTileTimelineIte
     @EpoxyAttribute
     lateinit var attributes: Attributes
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     @SuppressLint("SetTextI18n")
     override fun bind(holder: Holder) {

From 32e72f54b3906a7d95361f39452e0af5452916c9 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 12 Jan 2022 19:01:13 +0100
Subject: [PATCH 07/36] Bubbles: add quick settings (temporary)

---
 .../timeline/style/TimelineLayoutSettingsProvider.kt   |  9 +++++++--
 .../vector/app/features/settings/VectorPreferences.kt  | 10 ++++++++++
 .../src/main/res/xml/vector_settings_preferences.xml   |  5 +++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
index a10c95befe..9e351a706d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
@@ -16,11 +16,16 @@
 
 package im.vector.app.features.home.room.detail.timeline.style
 
+import im.vector.app.features.settings.VectorPreferences
 import javax.inject.Inject
 
-class TimelineLayoutSettingsProvider @Inject constructor() {
+class TimelineLayoutSettingsProvider @Inject constructor(private val vectorPreferences: VectorPreferences) {
 
     fun getLayoutSettings(): TimelineLayoutSettings {
-        return TimelineLayoutSettings.BUBBLE
+        return if (vectorPreferences.useMessageBubblesLayout()) {
+            TimelineLayoutSettings.BUBBLE
+        } else {
+            TimelineLayoutSettings.MODERN
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 3436c20ce3..9c472a387c 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -83,6 +83,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
         // interface
         const val SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY = "SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY"
         const val SETTINGS_INTERFACE_TEXT_SIZE_KEY = "SETTINGS_INTERFACE_TEXT_SIZE_KEY"
+        const val SETTINGS_INTERFACE_BUBBLE_KEY = "SETTINGS_INTERFACE_BUBBLE_KEY"
         const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY"
         private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY"
         private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY"
@@ -852,6 +853,15 @@ class VectorPreferences @Inject constructor(private val context: Context) {
         return defaultPrefs.getBoolean(SETTINGS_SHOW_EMOJI_KEYBOARD, true)
     }
 
+    /**
+     * Tells if the emoji keyboard button should be visible or not.
+     *
+     * @return true to show emoji keyboard button.
+     */
+    fun useMessageBubblesLayout(): Boolean {
+        return defaultPrefs.getBoolean(SETTINGS_INTERFACE_BUBBLE_KEY, false)
+    }
+
     /**
      * Tells if the rage shake is used.
      *
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index 14c7dc7b80..ac8a48fb2e 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -22,6 +22,11 @@
             android:title="@string/settings_theme"
             app:iconSpaceReserved="false" />
 
+        <im.vector.app.core.preference.VectorSwitchPreference
+            android:defaultValue="false"
+            android:key="SETTINGS_INTERFACE_BUBBLE_KEY"
+            android:title="Message bubbles" />
+
         <im.vector.app.core.preference.VectorPreference
             android:dialogTitle="@string/font_size"
             android:key="SETTINGS_INTERFACE_TEXT_SIZE_KEY"

From 37af93fba426d83971dd8131cee2ecd39fc6058b Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 12 Jan 2022 19:22:14 +0100
Subject: [PATCH 08/36] Bubbles: fix avatar/name visibility in modern layout

---
 .../style/TimelineMessageLayoutFactory.kt     | 27 +++++++++++--------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 18df8c133b..bd37c2d66a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -45,23 +45,28 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
                 ?: false
 
-        val showInformation =
-                (addDaySeparator ||
-                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
-                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
-                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
-                        isNextMessageReceivedMoreThanOneHourAgo ||
-                        isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()) && !isSentByMe
+        val showInformation = addDaySeparator ||
+                event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
+                event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
+                nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
+                isNextMessageReceivedMoreThanOneHourAgo ||
+                isTileTypeMessage(nextDisplayableEvent) ||
+                nextDisplayableEvent.isEdition()
 
         val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
-            TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps())
+            TimelineLayoutSettings.MODERN -> {
+                TimelineMessageLayout.Modern(
+                        showAvatar = showInformation,
+                        showDisplayName = showInformation,
+                        showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
+                )
+            }
             TimelineLayoutSettings.BUBBLE -> {
                 val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
                 val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
                 TimelineMessageLayout.Bubble(
-                        showAvatar = showInformation,
-                        showDisplayName = showInformation,
+                        showAvatar = showInformation && !isSentByMe,
+                        showDisplayName = showInformation && !isSentByMe,
                         isIncoming = !isSentByMe,
                         isFirstFromThisSender = isFirstFromThisSender,
                         isLastFromThisSender = isLastFromThisSender

From b9cc7959967aa2098b99eb9977b97b13aa308d1f Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 13 Jan 2022 12:33:36 +0100
Subject: [PATCH 09/36] Bubbles : fix background colors

---
 library/ui-styles/src/main/res/values/colors.xml     |  3 ---
 .../room/detail/timeline/view/MessageBubbleView.kt   | 12 ++++++++----
 .../res/drawable/bg_timeline_incoming_message.xml    |  4 ----
 .../res/drawable/bg_timeline_outgoing_message.xml    |  4 ----
 vector/src/main/res/layout/view_message_bubble.xml   |  1 -
 5 files changed, 8 insertions(+), 16 deletions(-)
 delete mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml
 delete mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml

diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index e3ec542c89..ca6f6d3142 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -13,9 +13,6 @@
     <color name="button_bot_background_color">#14368BD6</color>
     <color name="button_bot_enabled_text_color">@color/palette_azure</color>
 
-    <color name="bubble_background_outgoing">#0F0DBD8B</color>
-    <color name="bubble_background_incoming">@color/element_system_light</color>
-
     <!-- Notification (do not depends on theme) -->
     <color name="notification_accent_color">@color/palette_azure</color>
     <color name="key_share_req_accent_color">@color/palette_melon</color>
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 5bb732bdde..46e3edca44 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.home.room.detail.timeline.view
 
 import android.content.Context
+import android.content.res.ColorStateList
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
@@ -26,12 +27,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
+import androidx.core.graphics.ColorUtils
 import androidx.core.view.updateLayoutParams
 import com.google.android.material.shape.CornerFamily
 import com.google.android.material.shape.MaterialShapeDrawable
 import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0)
@@ -116,7 +119,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             }
             applyTo(bubbleView)
         }
-
     }
 
     private fun createBackgroundDrawable(): Drawable {
@@ -133,14 +135,16 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
         val backgroundColor: Int
         if (isIncoming) {
-            backgroundColor = R.color.bubble_background_incoming
+            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system)
             shapeAppearanceModelBuilder
                     .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setTopLeftCorner(topCornerFamily, topRadius)
                     .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
         } else {
-            backgroundColor = R.color.bubble_background_outgoing
+            val resolvedColor = ContextCompat.getColor(context, R.color.palette_element_green)
+            val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26
+            backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha)
             shapeAppearanceModelBuilder
                     .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
@@ -149,7 +153,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
         return MaterialShapeDrawable(shapeAppearanceModel).apply {
-            fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+            fillColor = ColorStateList.valueOf(backgroundColor)
         }
     }
 }
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
deleted file mode 100644
index 2cbca33702..0000000000
--- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?attr/vctr_system" />
-</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
deleted file mode 100644
index 0f75705a77..0000000000
--- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#0F0DBD8B" />
-</shape>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 8e9a95222a..8570a3dee2 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -91,7 +91,6 @@
             android:layout_marginStart="0dp"
             android:layout_marginEnd="0dp"
             android:addStatesFromChildren="true"
-            android:background="@drawable/bg_timeline_incoming_message"
             android:paddingStart="4dp"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent">

From baee076e41c628ff3897b80097212f8b8fd40ae9 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 13 Jan 2022 12:33:58 +0100
Subject: [PATCH 10/36] Bubbles: fix types using wrong layout

---
 .../timeline/factory/MessageItemFactory.kt    |  5 +-
 .../timeline/style/TimelineMessageLayout.kt   |  9 +--
 .../style/TimelineMessageLayoutFactory.kt     | 56 ++++++++++++++-----
 3 files changed, 49 insertions(+), 21 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 80343fd909..a80b948428 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -172,9 +172,7 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
-        return messageItem?.takeIf {
-            it.layout == R.layout.item_timeline_event_base
-        }?.apply {
+        return messageItem?.apply {
             layout(informationData.messageLayout.layoutRes)
         }
     }
@@ -650,6 +648,7 @@ class MessageItemFactory @Inject constructor(
     private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
                                   highlight: Boolean): RedactedMessageItem? {
         return RedactedMessageItem_()
+                .layout(attributes.informationData.messageLayout.layoutRes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .attributes(attributes)
                 .highlighted(highlight)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 48dba0a58a..50f4e95cee 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -27,10 +27,11 @@ sealed interface TimelineMessageLayout : Parcelable {
     val showTimestamp: Boolean
 
     @Parcelize
-    data class Modern(override val showAvatar: Boolean,
-                      override val showDisplayName: Boolean,
-                      override val showTimestamp: Boolean,
-                      override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout
+    data class Default(override val showAvatar: Boolean,
+                       override val showDisplayName: Boolean,
+                       override val showTimestamp: Boolean,
+                       // Keep defaultLayout generated on epoxy items
+                       override val layoutRes: Int = 0) : TimelineMessageLayout
 
     @Parcelize
     data class Bubble(override val showAvatar: Boolean,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index bd37c2d66a..9b2877f0f8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -21,6 +21,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
 import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
@@ -31,6 +32,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                                        private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
                                                        private val vectorPreferences: VectorPreferences) {
 
+    companion object {
+        private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
+                EventType.MESSAGE,
+                EventType.ENCRYPTED,
+                EventType.STICKER
+        )
+        private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
+                MessageType.MSGTYPE_POLL_START,
+                MessageType.MSGTYPE_VERIFICATION_REQUEST
+        )
+    }
+
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
 
         val event = params.event
@@ -55,27 +68,42 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
 
         val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
             TimelineLayoutSettings.MODERN -> {
-                TimelineMessageLayout.Modern(
-                        showAvatar = showInformation,
-                        showDisplayName = showInformation,
-                        showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
-                )
+                buildModernLayout(showInformation)
             }
             TimelineLayoutSettings.BUBBLE -> {
-                val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
-                TimelineMessageLayout.Bubble(
-                        showAvatar = showInformation && !isSentByMe,
-                        showDisplayName = showInformation && !isSentByMe,
-                        isIncoming = !isSentByMe,
-                        isFirstFromThisSender = isFirstFromThisSender,
-                        isLastFromThisSender = isLastFromThisSender
-                )
+                val type = event.root.getClearType()
+                if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
+                    val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null
+                    if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
+                        buildModernLayout(showInformation)
+                    }
+                    val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId
+                            || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+
+                    TimelineMessageLayout.Bubble(
+                            showAvatar = showInformation && !isSentByMe,
+                            showDisplayName = showInformation && !isSentByMe,
+                            isIncoming = !isSentByMe,
+                            isFirstFromThisSender = isFirstFromThisSender,
+                            isLastFromThisSender = isLastFromThisSender
+                    )
+                } else {
+                    buildModernLayout(showInformation)
+                }
             }
         }
         return messageLayout
     }
 
+    private fun buildModernLayout(showInformation: Boolean): TimelineMessageLayout.Default {
+        return TimelineMessageLayout.Default(
+                showAvatar = showInformation,
+                showDisplayName = showInformation,
+                showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
+        )
+    }
+
     /**
      * Tiles type message never show the sender information (like verification request), so we should repeat it for next message
      * even if same sender

From 5ac155285bcbf13992e3e2ff2d9352f30bbe0cb3 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 13 Jan 2022 13:14:37 +0100
Subject: [PATCH 11/36] Bubbles: some clean up

---
 tools/check/forbidden_strings_in_code.txt     |  2 +-
 .../app/features/home/AvatarRenderer.kt       |  1 -
 .../helper/MessageInformationDataFactory.kt   |  3 ++-
 .../timeline/style/TimelineMessageLayout.kt   | 21 ++++++++++++-------
 .../style/TimelineMessageLayoutFactory.kt     |  5 ++---
 .../detail/timeline/view/MessageBubbleView.kt |  4 ++--
 vector/src/main/res/values/strings.xml        |  1 +
 .../res/xml/vector_settings_preferences.xml   |  2 +-
 8 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index a0c7c2f4c6..cbfaf20247 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
 # android\.text\.TextUtils
 
 ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===115
+enum class===116
 
 ### Do not import temporary legacy classes
 import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index be689eb27a..2ee3233637 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -145,7 +145,6 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
                         }
                         else                    -> {
                             it.apply(RequestOptions.circleCropTransform())
-
                         }
                     }
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 2edab8af74..276802e574 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -65,7 +65,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
 
         val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 50f4e95cee..bd4be10783 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -30,16 +30,21 @@ sealed interface TimelineMessageLayout : Parcelable {
     data class Default(override val showAvatar: Boolean,
                        override val showDisplayName: Boolean,
                        override val showTimestamp: Boolean,
-                       // Keep defaultLayout generated on epoxy items
+            // Keep defaultLayout generated on epoxy items
                        override val layoutRes: Int = 0) : TimelineMessageLayout
 
     @Parcelize
-    data class Bubble(override val showAvatar: Boolean,
-                      override val showDisplayName: Boolean,
-                      override val showTimestamp: Boolean = true,
-                      val isIncoming: Boolean,
-                      val isFirstFromThisSender: Boolean,
-                      val isLastFromThisSender: Boolean,
-                      override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base,
+    data class Bubble(
+            override val showAvatar: Boolean,
+            override val showDisplayName: Boolean,
+            override val showTimestamp: Boolean = true,
+            val isIncoming: Boolean,
+            val isFirstFromThisSender: Boolean,
+            val isLastFromThisSender: Boolean,
+            override val layoutRes: Int = if (isIncoming) {
+                R.layout.item_timeline_event_bubble_incoming_base
+            } else {
+                R.layout.item_timeline_event_bubble_outgoing_base
+            },
     ) : TimelineMessageLayout
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 9b2877f0f8..6c26679105 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -45,7 +45,6 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
     }
 
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
-
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
         val prevDisplayableEvent = params.prevDisplayableEvent
@@ -78,8 +77,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                         buildModernLayout(showInformation)
                     }
                     val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId
-                            || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                            prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
                     TimelineMessageLayout.Bubble(
                             showAvatar = showInformation && !isSentByMe,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 46e3edca44..68e32861c3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -37,8 +37,8 @@ import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                                  defStyleAttr: Int = 0)
-    : RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
+                                                  defStyleAttr: Int = 0) :
+    RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
 
     override var isIncoming: Boolean = false
         set(value) {
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 34ac5fcddc..4e927701d7 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3701,4 +3701,5 @@
     <string name="poll_end_room_list_preview">Poll ended</string>
     <string name="delete_poll_dialog_title">Remove poll</string>
     <string name="delete_poll_dialog_content">Are you sure you want to remove this poll? You won\'t be able to recover it once removed.</string>
+    <string name="message_bubbles">Message bubbles</string>
 </resources>
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index ac8a48fb2e..a25562e3d2 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -25,7 +25,7 @@
         <im.vector.app.core.preference.VectorSwitchPreference
             android:defaultValue="false"
             android:key="SETTINGS_INTERFACE_BUBBLE_KEY"
-            android:title="Message bubbles" />
+            android:title="@string/message_bubbles" />
 
         <im.vector.app.core.preference.VectorPreference
             android:dialogTitle="@string/font_size"

From 1108ef9fbe16b3768a340e161de99f5105b805a3 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 14 Jan 2022 19:19:23 +0100
Subject: [PATCH 12/36] Bubbles: make it works for file, voice and polls. Also
 add parity for "modern" layout.

---
 .../src/main/res/drawable/bg_media_pill.xml   |   3 -
 .../main/res/drawable/file_progress_bar.xml   |   8 +-
 .../ui-styles/src/main/res/values/dimens.xml  |   3 +
 .../src/main/res/values/styles_progress.xml   |   1 +
 .../src/main/res/values/styles_timeline.xml   |  10 ++
 .../ContentDownloadStateTrackerBinder.kt      |  12 +--
 .../detail/timeline/item/MessageFileItem.kt   |  15 ++-
 .../timeline/item/MessageImageVideoItem.kt    |   5 +-
 .../detail/timeline/item/MessageVoiceItem.kt  |  13 +++
 .../style/TimelineMessageLayoutFactory.kt     |  27 ++---
 .../detail/timeline/view/MessageBubbleView.kt |  23 +++-
 .../res/drawable/overlay_bubble_media.xml     |   8 ++
 .../res/layout/item_timeline_event_base.xml   |   4 +-
 .../layout/item_timeline_event_file_stub.xml  | 102 ++++++++----------
 ...item_timeline_event_media_message_stub.xml |  12 +++
 .../res/layout/item_timeline_event_poll.xml   |   4 +-
 ...em_timeline_event_view_stubs_container.xml |   5 +-
 .../layout/item_timeline_event_voice_stub.xml |  29 ++---
 vector/src/main/res/layout/view_file_icon.xml |  14 +--
 .../main/res/layout/view_message_bubble.xml   |   2 +-
 .../layout/view_voice_message_recorder.xml    |   2 +-
 21 files changed, 171 insertions(+), 131 deletions(-)
 rename vector/src/main/res/drawable/bg_voice_playback.xml => library/ui-styles/src/main/res/drawable/bg_media_pill.xml (84%)
 create mode 100644 vector/src/main/res/drawable/overlay_bubble_media.xml

diff --git a/vector/src/main/res/drawable/bg_voice_playback.xml b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml
similarity index 84%
rename from vector/src/main/res/drawable/bg_voice_playback.xml
rename to library/ui-styles/src/main/res/drawable/bg_media_pill.xml
index 4474c00345..2ad9ca9918 100644
--- a/vector/src/main/res/drawable/bg_voice_playback.xml
+++ b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml
@@ -2,9 +2,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Tint color is provided by the theme -->
     <solid android:color="@android:color/black" />
-    <size
-        android:width="240dp"
-        android:height="44dp" />
     <corners
         android:bottomLeftRadius="12dp"
         android:bottomRightRadius="12dp"
diff --git a/library/ui-styles/src/main/res/drawable/file_progress_bar.xml b/library/ui-styles/src/main/res/drawable/file_progress_bar.xml
index d175b871f7..bc20bb6340 100644
--- a/library/ui-styles/src/main/res/drawable/file_progress_bar.xml
+++ b/library/ui-styles/src/main/res/drawable/file_progress_bar.xml
@@ -2,16 +2,14 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
     <item android:id="@android:id/background">
-        <shape>
-            <corners android:radius="8dp" />
-            <solid android:color="?vctr_room_active_widgets_banner_bg" />
+        <shape android:shape="oval">
+            <solid android:color="?vctr_system" />
         </shape>
     </item>
 
     <item android:id="@android:id/progress">
         <clip>
-            <shape>
-                <corners android:radius="8dp" />
+            <shape android:shape="oval">
                 <solid android:color="@color/vctr_notice_secondary_alpha12" />
             </shape>
         </clip>
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 6b6f1a3aef..3955bf44fd 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -15,6 +15,8 @@
     <dimen name="item_decoration_left_margin">72dp</dimen>
     <dimen name="item_event_message_state_size">16dp</dimen>
 
+    <dimen name="item_event_message_media_button_size">32dp</dimen>
+
     <dimen name="chat_avatar_size">40dp</dimen>
     <dimen name="member_list_avatar_size">60dp</dimen>
 
@@ -51,6 +53,7 @@
 
     <dimen name="chat_bubble_margin_start">28dp</dimen>
     <dimen name="chat_bubble_margin_end">62dp</dimen>
+    <dimen name="chat_bubble_fixed_size">300dp</dimen>
 
     <!--  Onboarding   -->
     <item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
diff --git a/library/ui-styles/src/main/res/values/styles_progress.xml b/library/ui-styles/src/main/res/values/styles_progress.xml
index 712e7e98b6..04a0e01b58 100644
--- a/library/ui-styles/src/main/res/values/styles_progress.xml
+++ b/library/ui-styles/src/main/res/values/styles_progress.xml
@@ -6,6 +6,7 @@
     <style name="Widget.Vector.ProgressBar.Horizontal.File">
         <item name="android:indeterminateOnly">false</item>
         <item name="android:progressDrawable">@drawable/file_progress_bar</item>
+        <item name="android:progressBackgroundTint">?android:colorBackground</item>
         <item name="android:minHeight">10dp</item>
         <item name="android:maxHeight">40dp</item>
     </style>
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index 7fd7eac0ec..ef2f694d3e 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -12,4 +12,14 @@
         <item name="android:layout_marginTop">4dp</item>
     </style>
 
+    <style name="TimelineContentMediaPillStyle">
+        <item name="android:paddingStart">8dp</item>
+        <item name="android:paddingEnd">8dp</item>
+        <item name="android:paddingTop">6dp</item>
+        <item name="android:paddingBottom">6dp</item>
+        <item name="minHeight">48dp</item>
+        <item name="android:background">@drawable/bg_media_pill</item>
+        <item name="android:backgroundTint">?vctr_content_quinary</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
index caf0131144..e4405570a6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
@@ -29,9 +29,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import javax.inject.Inject
 
 @ActivityScoped
-class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
-                                                            private val messageColorProvider: MessageColorProvider,
-                                                            private val errorFormatter: ErrorFormatter) {
+class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
 
     private val updateListeners = mutableMapOf<String, ContentDownloadUpdater>()
 
@@ -39,7 +37,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe
              holder: MessageFileItem.Holder) {
         activeSessionHolder.getSafeActiveSession()?.also { session ->
             val downloadStateTracker = session.contentDownloadProgressTracker()
-            val updateListener = ContentDownloadUpdater(holder, messageColorProvider, errorFormatter)
+            val updateListener = ContentDownloadUpdater(holder)
             updateListeners[mxcUrl] = updateListener
             downloadStateTracker.track(mxcUrl, updateListener)
         }
@@ -62,9 +60,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe
     }
 }
 
-private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder,
-                                     private val messageColorProvider: MessageColorProvider,
-                                     private val errorFormatter: ErrorFormatter) : ContentDownloadStateTracker.UpdateListener {
+private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder) : ContentDownloadStateTracker.UpdateListener {
 
     override fun onDownloadStateUpdate(state: ContentDownloadStateTracker.State) {
         when (state) {
@@ -124,7 +120,7 @@ private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder,
     private fun handleSuccess() {
         stop()
         holder.fileDownloadProgress.isIndeterminate = false
-        holder.fileDownloadProgress.progress = 100
+        holder.fileDownloadProgress.progress = 0
         holder.fileImageView.setImageResource(R.drawable.ic_paperclip)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
index bd35532eae..e736c0e2da 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
@@ -16,6 +16,8 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.graphics.Paint
 import android.view.ViewGroup
 import android.widget.ImageView
@@ -29,6 +31,8 @@ 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
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@@ -73,15 +77,19 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
         } else {
             if (izDownloaded) {
                 holder.fileImageView.setImageResource(iconRes)
-                holder.fileDownloadProgress.progress = 100
+                holder.fileDownloadProgress.progress = 0
             } else {
                 contentDownloadStateTrackerBinder.bind(mxcUrl, holder)
                 holder.fileImageView.setImageResource(R.drawable.ic_download)
-                holder.fileDownloadProgress.progress = 0
             }
         }
 //        holder.view.setOnClickListener(clickListener)
-
+        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+            Color.TRANSPARENT
+        }else {
+            ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
+        }
+        holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
         holder.filenameView.onClick(attributes.itemClickListener)
         holder.filenameView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.fileImageWrapper.onClick(attributes.itemClickListener)
@@ -98,6 +106,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
     override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
+        val mainLayout by bind<ViewGroup>(R.id.messageFileMainLayout)
         val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)
         val fileLayout by bind<ViewGroup>(R.id.messageFileLayout)
         val fileImageView by bind<ImageView>(R.id.messageFileIconView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index e20ad48b17..799cb1a002 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 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.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
@@ -71,7 +72,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
-        (holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = false
+        (holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = true
+        holder.overlayView.isVisible = baseAttributes.informationData.messageLayout is TimelineMessageLayout.Bubble
     }
 
     override fun unbind(holder: Holder) {
@@ -90,6 +92,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         val imageView by bind<ImageView>(R.id.messageThumbnailView)
         val playContentView by bind<ImageView>(R.id.messageMediaPlayView)
         val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)
+        val overlayView by bind<View>(R.id.messageMediaOverlayView)
     }
 
     companion object {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
index b6fa684eca..1058e3c1f9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
@@ -16,10 +16,14 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.text.format.DateUtils
+import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageButton
 import android.widget.TextView
+import androidx.core.content.ContextCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
@@ -29,6 +33,8 @@ import im.vector.app.core.epoxy.ClickListener
 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.helper.VoiceMessagePlaybackTracker
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.themes.ThemeUtils
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
@@ -80,6 +86,12 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
             }
         }
 
+        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+            Color.TRANSPARENT
+        }else {
+            ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
+        }
+        holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
         holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
 
         voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
@@ -123,6 +135,7 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
     override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
+        val voicePlaybackLayout by bind<View>(R.id.voicePlaybackLayout)
         val voiceLayout by bind<ViewGroup>(R.id.voiceLayout)
         val voicePlaybackControlButton by bind<ImageButton>(R.id.voicePlaybackControlButton)
         val voicePlaybackTime by bind<TextView>(R.id.voicePlaybackTime)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 6c26679105..f5ca97dc1d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -35,11 +35,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
     companion object {
         private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
                 EventType.MESSAGE,
+                EventType.POLL_START,
                 EventType.ENCRYPTED,
                 EventType.STICKER
         )
         private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
-                MessageType.MSGTYPE_POLL_START,
                 MessageType.MSGTYPE_VERIFICATION_REQUEST
         )
     }
@@ -72,21 +72,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
             TimelineLayoutSettings.BUBBLE -> {
                 val type = event.root.getClearType()
                 if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
-                    val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null
+                    val messageContent = event.getLastMessageContent()
                     if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
                         buildModernLayout(showInformation)
-                    }
-                    val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
-                            prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                    } else {
+                        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                                prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
-                    TimelineMessageLayout.Bubble(
-                            showAvatar = showInformation && !isSentByMe,
-                            showDisplayName = showInformation && !isSentByMe,
-                            isIncoming = !isSentByMe,
-                            isFirstFromThisSender = isFirstFromThisSender,
-                            isLastFromThisSender = isLastFromThisSender
-                    )
+                        TimelineMessageLayout.Bubble(
+                                showAvatar = showInformation && !isSentByMe,
+                                showDisplayName = showInformation && !isSentByMe,
+                                isIncoming = !isSentByMe,
+                                isFirstFromThisSender = isFirstFromThisSender,
+                                isLastFromThisSender = isLastFromThisSender,
+                        )
+                    }
                 } else {
                     buildModernLayout(showInformation)
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 68e32861c3..0a3746f8a4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -23,6 +23,7 @@ import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
+import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
@@ -38,7 +39,7 @@ import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0) :
-    RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
+        RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
 
     override var isIncoming: Boolean = false
         set(value) {
@@ -57,7 +58,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             render()
         }
 
-    override var showTimeAsOverlay: Boolean = true
+    override var showTimeAsOverlay: Boolean = false
         set(value) {
             field = value
             render()
@@ -69,7 +70,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
-            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, true)
+            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, false)
             isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
             isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
@@ -95,6 +96,9 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
             }
+            findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
         } else {
             val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
@@ -108,14 +112,23 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
             }
+            findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
         }
         ConstraintSet().apply {
             clone(bubbleView)
             clear(R.id.viewStubContainer, ConstraintSet.END)
             if (showTimeAsOverlay) {
-                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
-            } else {
+                val timeColor = ContextCompat.getColor(context, R.color.palette_white)
+                findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
                 connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
+                val margin = resources.getDimensionPixelSize(R.dimen.layout_horizontal_margin)
+                setMargin(R.id.messageTimeView, ConstraintSet.END, margin)
+            } else {
+                val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
+                findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
             }
             applyTo(bubbleView)
         }
diff --git a/vector/src/main/res/drawable/overlay_bubble_media.xml b/vector/src/main/res/drawable/overlay_bubble_media.xml
new file mode 100644
index 0000000000..ce34a39037
--- /dev/null
+++ b/vector/src/main/res/drawable/overlay_bubble_media.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:type="linear"
+        android:angle="270"
+        android:startColor="#00000000"
+        android:endColor="#33000000"/>
+</shape>
\ No newline at end of file
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 debf578519..bc9ed68232 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -78,14 +78,14 @@
 
     <include
         android:id="@+id/viewStubContainer"
+        layout="@layout/item_timeline_event_view_stubs_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        layout="@layout/item_timeline_event_view_stubs_container"
         android:layout_below="@id/messageMemberNameView"
         android:layout_marginEnd="8dp"
         android:layout_toStartOf="@id/messageSendStateImageView"
         android:layout_toEndOf="@id/messageStartGuideline"
-        android:addStatesFromChildren="true"/>
+        android:addStatesFromChildren="true" />
 
     <im.vector.app.core.ui.views.SendStateImageView
         android:id="@+id/messageSendStateImageView"
diff --git a/vector/src/main/res/layout/item_timeline_event_file_stub.xml b/vector/src/main/res/layout/item_timeline_event_file_stub.xml
index 8a422ed177..41e4a118a3 100644
--- a/vector/src/main/res/layout/item_timeline_event_file_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_file_stub.xml
@@ -1,80 +1,62 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+
+<LinearLayout 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/messageFileLayout"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
+    android:orientation="vertical"
     tools:viewBindingIgnore="true">
 
-    <im.vector.app.core.ui.views.ShieldImageView
-        android:id="@+id/messageFilee2eIcon"
-        android:layout_width="14dp"
-        android:layout_height="14dp"
-        android:visibility="gone"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:visibility="visible" />
-
-    <!-- the media type -->
-    <RelativeLayout
-        android:id="@+id/messageFileImageView"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:layout_marginStart="4dp"
-        app:layout_constraintStart_toEndOf="@id/messageFilee2eIcon"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <include layout="@layout/view_file_icon" />
-    </RelativeLayout>
-
-    <!--    <ImageView-->
-    <!--        android:id="@+id/messageFileImageView"-->
-    <!--        android:layout_width="@dimen/chat_avatar_size"-->
-    <!--        android:layout_height="@dimen/chat_avatar_size"-->
-    <!--        android:layout_marginStart="4dp"-->
-    <!--        app:layout_constraintStart_toEndOf="@id/messageFilee2eIcon"-->
-    <!--        app:layout_constraintTop_toTopOf="parent"-->
-    <!--        tools:src="@drawable/filetype_attachment" />-->
-
-    <!-- the media -->
-    <TextView
-        android:id="@+id/messageFilenameView"
-        style="@style/Widget.Vector.TextView.Body"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/messageFileMainLayout"
+        style="@style/TimelineContentMediaPillStyle"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="4dp"
-        android:layout_marginEnd="32dp"
-        android:autoLink="none"
-        android:gravity="center_vertical"
-        android:minHeight="@dimen/chat_avatar_size"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0"
-        app:layout_constraintStart_toEndOf="@id/messageFileImageView"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="A filename here" />
+        tools:viewBindingIgnore="true">
 
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/horizontalBarrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="messageFileImageView,messageFilenameView" />
+        <FrameLayout
+            android:id="@+id/messageFileImageView"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
+            <include layout="@layout/view_file_icon" />
+
+        </FrameLayout>
+
+        <!-- the file name-->
+        <TextView
+            android:id="@+id/messageFilenameView"
+            style="@style/Widget.Vector.TextView.Body"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:autoLink="none"
+            android:maxLines="1"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/messageFileImageView"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            tools:text="A filename here" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <include
         android:id="@+id/messageFileUploadProgressLayout"
         layout="@layout/media_upload_download_progress_layout"
-        android:layout_width="0dp"
-        android:layout_height="46dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
         android:layout_marginEnd="32dp"
         android:visibility="gone"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/horizontalBarrier"
         tools:visibility="visible" />
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index 68abfbfda8..bdfa0c5164 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -18,12 +18,24 @@
         tools:layout_height="300dp"
         tools:src="@tools:sample/backgrounds/scenic" />
 
+    <View
+        android:id="@+id/messageMediaOverlayView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/overlay_bubble_media"
+        android:elevation="2dp"
+        app:layout_constraintStart_toStartOf="@id/messageThumbnailView"
+        app:layout_constraintEnd_toEndOf="@id/messageThumbnailView"
+        app:layout_constraintTop_toTopOf="@id/messageThumbnailView"
+        app:layout_constraintBottom_toBottomOf="@id/messageThumbnailView"/>
+
     <ImageView
         android:id="@+id/messageMediaPlayView"
         android:layout_width="40dp"
         android:layout_height="40dp"
         android:contentDescription="@string/action_play"
         android:src="@drawable/ic_material_play_circle"
+        app:tint="?vctr_system"
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="@id/messageThumbnailView"
         app:layout_constraintEnd_toEndOf="@id/messageThumbnailView"
diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml
index 575b2b4d4e..64444e2c69 100644
--- a/vector/src/main/res/layout/item_timeline_event_poll.xml
+++ b/vector/src/main/res/layout/item_timeline_event_poll.xml
@@ -2,17 +2,19 @@
 <androidx.constraintlayout.widget.ConstraintLayout 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:minWidth="@dimen/chat_bubble_fixed_size"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
     <TextView
         android:id="@+id/questionTextView"
         style="@style/Widget.Vector.TextView.Subtitle"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
         android:textColor="?vctr_content_primary"
         android:textStyle="bold"
+        app:layout_constraintHorizontal_bias="0"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
index 680db8ca51..576b2d4f01 100644
--- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
+++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
@@ -30,6 +30,7 @@
         android:id="@+id/messageContentFileStub"
         style="@style/TimelineContentStubBaseParams"
         android:layout_height="wrap_content"
+        tools:visibility="gone"
         android:layout="@layout/item_timeline_event_file_stub" />
 
     <ViewStub
@@ -41,12 +42,14 @@
     <ViewStub
         android:id="@+id/messageContentVoiceStub"
         style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_voice_stub"
-        tools:visibility="visible" />
+        tools:visibility="gone" />
 
     <ViewStub
         android:id="@+id/messageContentPollStub"
         style="@style/TimelineContentStubBaseParams"
+        android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_poll" />
 
 
diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
index 5cdd5a815a..a180afbf8e 100644
--- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
@@ -1,31 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout 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/voiceLayout"
+    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:minWidth="@dimen/chat_bubble_fixed_size"
     tools:viewBindingIgnore="true">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/voicePlaybackLayout"
-        android:layout_width="0dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/bg_voice_playback"
-        android:backgroundTint="?vctr_content_quinary"
-        android:minHeight="48dp"
-        android:paddingStart="8dp"
-        android:paddingTop="6dp"
-        android:paddingEnd="8dp"
-        android:paddingBottom="6dp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent">
+        style="@style/TimelineContentMediaPillStyle">
 
         <ImageButton
             android:id="@+id/voicePlaybackControlButton"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
+            android:layout_width="@dimen/item_event_message_media_button_size"
+            android:layout_height="@dimen/item_event_message_media_button_size"
             android:background="@drawable/bg_voice_play_pause_button"
             android:backgroundTint="?android:colorBackground"
             android:contentDescription="@string/a11y_play_voice_message"
@@ -65,16 +58,12 @@
     <include
         android:id="@+id/messageFileUploadProgressLayout"
         layout="@layout/media_upload_download_progress_layout"
-        android:layout_width="0dp"
+        android:layout_width="match_parent"
         android:layout_height="46dp"
         android:layout_marginStart="8dp"
         android:layout_marginTop="8dp"
         android:layout_marginEnd="32dp"
         android:visibility="gone"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/voicePlaybackLayout"
         tools:visibility="visible" />
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>
diff --git a/vector/src/main/res/layout/view_file_icon.xml b/vector/src/main/res/layout/view_file_icon.xml
index 1c5268a50b..db88802ba1 100644
--- a/vector/src/main/res/layout/view_file_icon.xml
+++ b/vector/src/main/res/layout/view_file_icon.xml
@@ -2,22 +2,22 @@
 <merge 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:layout_width="50dp"
-    android:layout_height="50dp"
-    tools:parentTag="android.widget.RelativeLayout">
+    android:layout_width="32dp"
+    android:layout_height="32dp"
+    tools:parentTag="android.widget.FrameLayout">
 
     <ProgressBar
         android:id="@+id/messageFileProgressbar"
         style="@style/Widget.Vector.ProgressBar.Horizontal.File"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:progress="40" />
+        tools:progress="40" />
 
     <ImageView
         android:id="@+id/messageFileIconView"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
-        android:layout_centerInParent="true"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:layout_gravity="center"
         android:contentDescription="@string/attachment_type_file"
         android:src="@drawable/ic_download"
         app:tint="?vctr_notice_secondary"
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 8570a3dee2..72e8f7dc29 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -105,7 +105,7 @@
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
-                app:layout_constraintWidth_max="300dp" />
+                app:layout_constraintWidth_max="@dimen/chat_bubble_fixed_size" />
 
             <TextView
                 android:id="@+id/messageTimeView"
diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml
index 31a513d00b..70711e71a6 100644
--- a/vector/src/main/res/layout/view_voice_message_recorder.xml
+++ b/vector/src/main/res/layout/view_voice_message_recorder.xml
@@ -160,7 +160,7 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:layout_width="0dp"
             android:layout_height="0dp"
-            android:background="@drawable/bg_voice_playback"
+            android:background="@drawable/bg_media_pill"
             android:backgroundTint="?vctr_content_quinary"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"

From a9e7c45074343210d73bee0f927521fe9de7aa4d Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 18 Jan 2022 19:26:23 +0100
Subject: [PATCH 13/36] Fix url preview sizing

---
 ...ageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} | 2 ++
 vector/src/main/res/layout/view_url_preview.xml                 | 2 ++
 2 files changed, 4 insertions(+)
 rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/{MessageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} (94%)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
similarity index 94%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
index bfb5884674..23dd9860f8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
@@ -21,4 +21,6 @@ interface MessageViewConfiguration {
     var isFirstFromSender: Boolean
     var isLastFromSender: Boolean
     var showTimeAsOverlay: Boolean
+    var showNoBubble: Boolean
+    fun render()
 }
diff --git a/vector/src/main/res/layout/view_url_preview.xml b/vector/src/main/res/layout/view_url_preview.xml
index 93ea4ea2bb..ff4ab1ed9a 100644
--- a/vector/src/main/res/layout/view_url_preview.xml
+++ b/vector/src/main/res/layout/view_url_preview.xml
@@ -9,6 +9,7 @@
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:minWidth="@dimen/chat_bubble_fixed_size"
         android:orientation="vertical">
 
         <ImageView
@@ -17,6 +18,7 @@
             android:layout_height="wrap_content"
             android:adjustViewBounds="true"
             android:importantForAccessibility="no"
+            android:layout_gravity="center"
             android:maxHeight="200dp"
             android:scaleType="fitXY"
             tools:src="@tools:sample/backgrounds/scenic" />

From 5ee4984ec843e073630f659f2d54f29e86110e03 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 18 Jan 2022 19:27:12 +0100
Subject: [PATCH 14/36] Bubbles: handle images and make small refactoring

---
 .../ui-styles/src/main/res/values/dimens.xml  |   1 +
 .../src/main/res/values/styles_timeline.xml   |   6 -
 .../timeline/item/AbsBaseMessageItem.kt       |   7 +-
 .../timeline/item/MessageImageVideoItem.kt    |  24 ++-
 .../timeline/style/TimelineMessageLayout.kt   |   3 +-
 .../style/TimelineMessageLayoutFactory.kt     |   8 +
 .../detail/timeline/view/MessageBubbleView.kt | 148 +++++++++---------
 .../view/TimelineMessageLayoutRenderer.kt     |  11 +-
 .../features/media/ImageContentRenderer.kt    |   6 +-
 .../res/drawable/overlay_bubble_media.xml     |   1 +
 ...em_timeline_event_view_stubs_container.xml |  25 +--
 .../main/res/layout/view_message_bubble.xml   |   4 +-
 12 files changed, 131 insertions(+), 113 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index d89b3f477f..15e2fc2fbb 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -54,6 +54,7 @@
     <dimen name="chat_bubble_margin_start">28dp</dimen>
     <dimen name="chat_bubble_margin_end">62dp</dimen>
     <dimen name="chat_bubble_fixed_size">300dp</dimen>
+    <dimen name="chat_bubble_corner_radius">12dp</dimen>
 
     <!--  Onboarding   -->
     <item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index ef2f694d3e..127d9f3dc7 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -4,12 +4,6 @@
     <style name="TimelineContentStubBaseParams">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_marginStart">8dp</item>
-        <item name="android:layout_marginLeft">8dp</item>
-        <item name="android:layout_marginEnd">8dp</item>
-        <item name="android:layout_marginRight">8dp</item>
-        <item name="android:layout_marginBottom">4dp</item>
-        <item name="android:layout_marginTop">4dp</item>
     </style>
 
     <style name="TimelineContentMediaPillStyle">
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 3d9db4e827..ac9ef1522d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -29,7 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView
 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.view.MessageViewConfiguration
+import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
 import im.vector.app.features.reactions.widget.ReactionButton
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -99,10 +99,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
 
         holder.view.onClick(baseAttributes.itemClickListener)
         holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
-        (holder.view as? MessageViewConfiguration)?.apply {
-            isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender
-            isLastFromSender = baseAttributes.informationData.isLastFromThisSender
-        }
+        (holder.view as? TimelineMessageLayoutRenderer)?.render(baseAttributes.informationData.messageLayout)
     }
 
     override fun unbind(holder: H) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 799cb1a002..c9876da2f2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -23,14 +23,17 @@ import androidx.core.view.ViewCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
+import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
+import im.vector.app.core.utils.DimensionConverter
 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.home.room.detail.timeline.view.MessageViewConfiguration
+import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
 import im.vector.app.features.media.ImageContentRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -56,7 +59,21 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
 
     override fun bind(holder: Holder) {
         super.bind(holder)
-        imageContentRenderer.render(mediaData, mode, holder.imageView)
+        val messageLayout = baseAttributes.informationData.messageLayout
+        val dimensionConverter = DimensionConverter(holder.view.resources)
+        val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
+            val cornerRadius = holder.view.resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
+            val topRadius = if (messageLayout.isFirstFromThisSender) cornerRadius else 0f
+            val bottomRadius = if (messageLayout.isLastFromThisSender) cornerRadius else 0f
+            if (messageLayout.isIncoming) {
+                GranularRoundedCorners(topRadius, cornerRadius, cornerRadius, bottomRadius)
+            } else {
+                GranularRoundedCorners(cornerRadius, topRadius, bottomRadius, cornerRadius)
+            }
+        } else {
+            RoundedCorners(dimensionConverter.dpToPx(8))
+        }
+        imageContentRenderer.render(mediaData, mode, holder.imageView, imageCornerTransformation)
         if (!attributes.informationData.sendState.hasFailed()) {
             contentUploadStateTrackerBinder.bind(
                     attributes.informationData.eventId,
@@ -72,8 +89,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
-        (holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = true
-        holder.overlayView.isVisible = baseAttributes.informationData.messageLayout is TimelineMessageLayout.Bubble
+        holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble
     }
 
     override fun unbind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index bd4be10783..5d487c5d23 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -41,10 +41,11 @@ sealed interface TimelineMessageLayout : Parcelable {
             val isIncoming: Boolean,
             val isFirstFromThisSender: Boolean,
             val isLastFromThisSender: Boolean,
+            val isPseudoBubble: Boolean,
             override val layoutRes: Int = if (isIncoming) {
                 R.layout.item_timeline_event_bubble_incoming_base
             } else {
                 R.layout.item_timeline_event_bubble_outgoing_base
-            },
+            }
     ) : TimelineMessageLayout
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index f5ca97dc1d..685f66f40e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -33,15 +33,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                                        private val vectorPreferences: VectorPreferences) {
 
     companion object {
+        // Can't be rendered in bubbles, so get back to default layout
         private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
                 EventType.MESSAGE,
                 EventType.POLL_START,
                 EventType.ENCRYPTED,
                 EventType.STICKER
         )
+        // Can't be rendered in bubbles, so get back to default layout
         private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
                 MessageType.MSGTYPE_VERIFICATION_REQUEST
         )
+
+        // Use the bubble layout but without borders
+        private val MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT = setOf(
+                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO,
+        )
     }
 
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
@@ -86,6 +93,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                 isIncoming = !isSentByMe,
                                 isFirstFromThisSender = isFirstFromThisSender,
                                 isLastFromThisSender = isLastFromThisSender,
+                                isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
                         )
                     }
                 } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 0a3746f8a4..4731b76934 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -18,13 +18,12 @@ package im.vector.app.features.home.room.detail.timeline.view
 
 import android.content.Context
 import android.content.res.ColorStateList
+import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
-import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
@@ -35,70 +34,38 @@ import com.google.android.material.shape.MaterialShapeDrawable
 import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.databinding.ViewMessageBubbleBinding
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 import im.vector.app.features.themes.ThemeUtils
+import timber.log.Timber
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0) :
-        RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
+        RelativeLayout(context, attrs, defStyleAttr), TimelineMessageLayoutRenderer {
 
-    override var isIncoming: Boolean = false
-        set(value) {
-            field = value
-            render()
-        }
+    private var isIncoming: Boolean = false
 
-    override var isFirstFromSender: Boolean = false
-        set(value) {
-            field = value
-            render()
-        }
-    override var isLastFromSender: Boolean = false
-        set(value) {
-            field = value
-            render()
-        }
+    private val cornerRadius = resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
+    private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12)
+    private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
 
-    override var showTimeAsOverlay: Boolean = false
-        set(value) {
-            field = value
-            render()
-        }
-
-    private val cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
+    private lateinit var views: ViewMessageBubbleBinding
 
     init {
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
-            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, false)
-            isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
-            isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
     }
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        render()
-    }
-
-    private fun render() {
+        views = ViewMessageBubbleBinding.bind(this)
         val currentLayoutDirection = layoutDirection
-        val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView)
-        bubbleView.apply {
-            background = createBackgroundDrawable()
-            outlineProvider = ViewOutlineProvider.BACKGROUND
-            clipToOutline = true
-        }
         if (isIncoming) {
-            findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
-            findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
-            bubbleView.layoutDirection = currentLayoutDirection
-            findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
-                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
-            }
-            findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
-                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
-            }
+            views.informationBottom.layoutDirection = currentLayoutDirection
+            views.bubbleWrapper.layoutDirection = currentLayoutDirection
+            views.bubbleView.layoutDirection = currentLayoutDirection
         } else {
             val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
@@ -106,41 +73,66 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
                 View.LAYOUT_DIRECTION_LTR
             }
 
-            findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
-            findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
-            bubbleView.layoutDirection = currentLayoutDirection
-            findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
-                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
-            }
-            findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
-                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
-            }
-        }
-        ConstraintSet().apply {
-            clone(bubbleView)
-            clear(R.id.viewStubContainer, ConstraintSet.END)
-            if (showTimeAsOverlay) {
-                val timeColor = ContextCompat.getColor(context, R.color.palette_white)
-                findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
-                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
-                val margin = resources.getDimensionPixelSize(R.dimen.layout_horizontal_margin)
-                setMargin(R.id.messageTimeView, ConstraintSet.END, margin)
-            } else {
-                val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
-                findViewById<TextView>(R.id.messageTimeView).setTextColor(timeColor)
-                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
-            }
-            applyTo(bubbleView)
+            views.informationBottom.layoutDirection = oppositeLayoutDirection
+            views.bubbleWrapper.layoutDirection = oppositeLayoutDirection
+            views.bubbleView.layoutDirection = currentLayoutDirection
         }
     }
 
-    private fun createBackgroundDrawable(): Drawable {
-        val (topCornerFamily, topRadius) = if (isFirstFromSender) {
+    override fun render(messageLayout: TimelineMessageLayout) {
+        if (messageLayout !is TimelineMessageLayout.Bubble) {
+            Timber.v("Can't render messageLayout $messageLayout")
+            return
+        }
+        views.bubbleView.apply {
+            background = createBackgroundDrawable(messageLayout)
+            outlineProvider = ViewOutlineProvider.BACKGROUND
+            clipToOutline = true
+        }
+        ConstraintSet().apply {
+            clone(views.bubbleView)
+            clear(R.id.viewStubContainer, ConstraintSet.END)
+            val showTimeAsOverlay = messageLayout.isPseudoBubble
+            if (showTimeAsOverlay) {
+                val timeColor = ContextCompat.getColor(context, R.color.palette_white)
+                views.messageTimeView.setTextColor(timeColor)
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
+            } else {
+                val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
+                views.messageTimeView.setTextColor(timeColor)
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
+            }
+            applyTo(views.bubbleView)
+        }
+        if (messageLayout.isPseudoBubble) {
+            views.viewStubContainer.root.setPadding(0, 0, 0, 0)
+        } else {
+            views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)
+        }
+        if (messageLayout.isIncoming) {
+            views.messageEndGuideline.updateLayoutParams<LayoutParams> {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
+            views.messageStartGuideline.updateLayoutParams<LayoutParams> {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
+        } else {
+            views.messageEndGuideline.updateLayoutParams<LayoutParams> {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
+            views.messageStartGuideline.updateLayoutParams<LayoutParams> {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
+        }
+    }
+
+    private fun createBackgroundDrawable(messageLayout: TimelineMessageLayout.Bubble): Drawable {
+        val (topCornerFamily, topRadius) = if (messageLayout.isFirstFromThisSender) {
             Pair(CornerFamily.ROUNDED, cornerRadius)
         } else {
             Pair(CornerFamily.CUT, 0f)
         }
-        val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) {
+        val (bottomCornerFamily, bottomRadius) = if (messageLayout.isLastFromThisSender) {
             Pair(CornerFamily.ROUNDED, cornerRadius)
         } else {
             Pair(CornerFamily.CUT, 0f)
@@ -166,7 +158,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
         return MaterialShapeDrawable(shapeAppearanceModel).apply {
-            fillColor = ColorStateList.valueOf(backgroundColor)
+            fillColor = if (messageLayout.isPseudoBubble) {
+                ColorStateList.valueOf(Color.TRANSPARENT)
+            } else {
+                ColorStateList.valueOf(backgroundColor)
+            }
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
index 23dd9860f8..a339119f46 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
@@ -16,11 +16,8 @@
 
 package im.vector.app.features.home.room.detail.timeline.view
 
-interface MessageViewConfiguration {
-    var isIncoming: Boolean
-    var isFirstFromSender: Boolean
-    var isLastFromSender: Boolean
-    var showTimeAsOverlay: Boolean
-    var showNoBubble: Boolean
-    fun render()
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+
+interface TimelineMessageLayoutRenderer {
+    fun render(messageLayout: TimelineMessageLayout)
 }
diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
index 8d6d1f467b..be9d24272d 100644
--- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.media
 
+import android.graphics.Bitmap
 import android.graphics.drawable.Drawable
 import android.net.Uri
 import android.os.Parcelable
@@ -23,6 +24,7 @@ import android.view.View
 import android.widget.ImageView
 import androidx.core.view.updateLayoutParams
 import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.Transformation
 import com.bumptech.glide.load.engine.DiskCacheStrategy
 import com.bumptech.glide.load.engine.GlideException
 import com.bumptech.glide.load.resource.bitmap.RoundedCorners
@@ -109,7 +111,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
                 .into(imageView)
     }
 
-    fun render(data: Data, mode: Mode, imageView: ImageView) {
+    fun render(data: Data, mode: Mode, imageView: ImageView, cornerTransformation: Transformation<Bitmap> = RoundedCorners(dimensionConverter.dpToPx(8))) {
         val size = processSize(data, mode)
         imageView.updateLayoutParams {
             width = size.width
@@ -120,7 +122,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
 
         createGlideRequest(data, mode, imageView, size)
                 .dontAnimate()
-                .transform(RoundedCorners(dimensionConverter.dpToPx(8)))
+                .transform(cornerTransformation)
                 // .thumbnail(0.3f)
                 .into(imageView)
     }
diff --git a/vector/src/main/res/drawable/overlay_bubble_media.xml b/vector/src/main/res/drawable/overlay_bubble_media.xml
index ce34a39037..8bc9c58064 100644
--- a/vector/src/main/res/drawable/overlay_bubble_media.xml
+++ b/vector/src/main/res/drawable/overlay_bubble_media.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/chat_bubble_corner_radius"/>
     <gradient
         android:type="linear"
         android:angle="270"
diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
index 576b2d4f01..440a1f4602 100644
--- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
+++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
@@ -3,18 +3,24 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:addStatesFromChildren="true">
+    android:addStatesFromChildren="true"
+    android:paddingStart="8dp"
+    android:paddingLeft="8dp"
+    android:paddingTop="4dp"
+    android:paddingEnd="8dp"
+    android:paddingRight="8dp"
+    android:paddingBottom="4dp">
 
     <ViewStub
         android:id="@+id/messageContentTextStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_text_message_stub"
         tools:visibility="visible" />
 
     <ViewStub
         android:id="@+id/messageContentCodeBlockStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_code_block_stub"
         tools:visibility="visible" />
@@ -22,33 +28,34 @@
     <ViewStub
         android:id="@+id/messageContentMediaStub"
         style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:inflatedId="@+id/messageContentMedia"
         android:layout="@layout/item_timeline_event_media_message_stub" />
 
     <ViewStub
         android:id="@+id/messageContentFileStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        tools:visibility="gone"
-        android:layout="@layout/item_timeline_event_file_stub" />
+        android:layout="@layout/item_timeline_event_file_stub"
+        tools:visibility="gone" />
 
     <ViewStub
         android:id="@+id/messageContentRedactedStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_redacted_stub" />
 
     <ViewStub
         android:id="@+id/messageContentVoiceStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_voice_stub"
         tools:visibility="gone" />
 
     <ViewStub
         android:id="@+id/messageContentPollStub"
-        style="@style/TimelineContentStubBaseParams"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout="@layout/item_timeline_event_poll" />
 
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 72e8f7dc29..d6263b96d7 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -4,8 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    tools:parentTag="android.widget.RelativeLayout"
-    tools:viewBindingIgnore="true">
+    tools:parentTag="android.widget.RelativeLayout">
 
     <im.vector.app.core.platform.CheckableView
         android:id="@+id/messageSelectedBackground"
@@ -91,7 +90,6 @@
             android:layout_marginStart="0dp"
             android:layout_marginEnd="0dp"
             android:addStatesFromChildren="true"
-            android:paddingStart="4dp"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent">
 

From ac0c2624f0818ea0c57d472272950cc1a7bf861a Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 19 Jan 2022 11:49:33 +0100
Subject: [PATCH 15/36] Bubbles: update sticker handling

---
 .../room/detail/timeline/item/MessageImageVideoItem.kt   | 2 +-
 .../room/detail/timeline/style/TimelineMessageLayout.kt  | 1 +
 .../timeline/style/TimelineMessageLayoutFactory.kt       | 9 +++++++--
 .../home/room/detail/timeline/view/MessageBubbleView.kt  | 3 +--
 4 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index c9876da2f2..274841973e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -89,7 +89,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
-        holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble
+        holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble && messageLayout.timestampAsOverlay
     }
 
     override fun unbind(holder: Holder) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 5d487c5d23..bdcbc52037 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -42,6 +42,7 @@ sealed interface TimelineMessageLayout : Parcelable {
             val isFirstFromThisSender: Boolean,
             val isLastFromThisSender: Boolean,
             val isPseudoBubble: Boolean,
+            val timestampAsOverlay: Boolean,
             override val layoutRes: Int = if (isIncoming) {
                 R.layout.item_timeline_event_bubble_incoming_base
             } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 685f66f40e..e12e27472c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -40,6 +40,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                 EventType.ENCRYPTED,
                 EventType.STICKER
         )
+
         // Can't be rendered in bubbles, so get back to default layout
         private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
                 MessageType.MSGTYPE_VERIFICATION_REQUEST
@@ -47,7 +48,10 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
 
         // Use the bubble layout but without borders
         private val MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT = setOf(
-                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO,
+                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_STICKER_LOCAL
+        )
+        private val MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY = setOf(
+                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO
         )
     }
 
@@ -93,7 +97,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                 isIncoming = !isSentByMe,
                                 isFirstFromThisSender = isFirstFromThisSender,
                                 isLastFromThisSender = isLastFromThisSender,
-                                isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
+                                isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT,
+                                timestampAsOverlay = messageContent?.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
                         )
                     }
                 } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 4731b76934..d36e3068b7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -92,8 +92,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         ConstraintSet().apply {
             clone(views.bubbleView)
             clear(R.id.viewStubContainer, ConstraintSet.END)
-            val showTimeAsOverlay = messageLayout.isPseudoBubble
-            if (showTimeAsOverlay) {
+            if (messageLayout.timestampAsOverlay) {
                 val timeColor = ContextCompat.getColor(context, R.color.palette_white)
                 views.messageTimeView.setTextColor(timeColor)
                 connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)

From 2d9454c5b655f85b3841b2b21a52879c49a291f4 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 19 Jan 2022 16:19:47 +0100
Subject: [PATCH 16/36] Bubbles: first iteration on url preview

---
 .../home/room/detail/timeline/item/MessageTextItem.kt    | 9 +++++++++
 .../home/room/detail/timeline/url/PreviewUrlView.kt      | 1 -
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 12e5d6a8dd..60fe1a4b2c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.graphics.Color
 import android.text.Spanned
 import android.text.method.MovementMethod
 import androidx.appcompat.widget.AppCompatTextView
@@ -29,11 +30,13 @@ import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.epoxy.onLongClickIgnoringLinks
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
 import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
 import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState
 import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView
 import im.vector.app.features.media.ImageContentRenderer
+import im.vector.app.features.themes.ThemeUtils
 import io.noties.markwon.MarkwonPlugin
 import org.matrix.android.sdk.api.extensions.orFalse
 
@@ -80,6 +83,12 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
             safePreviewUrlRetriever.addListener(attributes.informationData.eventId, previewUrlViewUpdater)
         }
         holder.previewUrlView.delegate = previewUrlCallback
+        val urlPreviewBackgroundColor = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
+            Color.TRANSPARENT
+        } else {
+            ThemeUtils.getColor(holder.view.context, R.attr.vctr_system)
+        }
+        holder.previewUrlView.setCardBackgroundColor(urlPreviewBackgroundColor)
 
         if (useBigFont) {
             holder.messageView.textSize = 44F
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
index 631f00819c..e41f62c373 100755
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
@@ -47,7 +47,6 @@ class PreviewUrlView @JvmOverloads constructor(
         setupView()
         radius = resources.getDimensionPixelSize(R.dimen.preview_url_view_corner_radius).toFloat()
         cardElevation = 0f
-        setCardBackgroundColor(ThemeUtils.getColor(context, R.attr.vctr_system))
     }
 
     private var state: PreviewUrlUiState = PreviewUrlUiState.Unknown

From 8c4dff4db9f8443efecda3d6c82cf5e33cca310d Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 20 Jan 2022 15:13:54 +0100
Subject: [PATCH 17/36] Bubbles: change again url preview

---
 .../ui-styles/src/main/res/values/dimens.xml  |  1 +
 .../sdk/api/session/media/PreviewUrlData.kt   |  6 +-
 .../database/RealmSessionStoreMigration.kt    | 14 +++-
 .../database/model/PreviewUrlCacheEntity.kt   |  3 +-
 .../session/media/GetPreviewUrlTask.kt        | 11 ++-
 .../session/media/PreviewUrlMapper.kt         |  4 +-
 .../detail/timeline/item/MessageTextItem.kt   |  7 +-
 .../detail/timeline/url/PreviewUrlView.kt     | 26 ++++++-
 .../features/media/ImageContentRenderer.kt    | 18 ++++-
 .../item_timeline_event_text_message_stub.xml |  5 +-
 .../src/main/res/layout/view_url_preview.xml  | 77 ++++++++++++-------
 11 files changed, 123 insertions(+), 49 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 15e2fc2fbb..04ed7fcc55 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -44,6 +44,7 @@
 
     <!--  Preview Url  -->
     <dimen name="preview_url_view_corner_radius">8dp</dimen>
+    <dimen name="preview_url_view_image_max_height">160dp</dimen>
 
     <!-- Composer -->
     <dimen name="composer_min_height">56dp</dimen>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt
index 33fc8b052b..bfba43a82d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt
@@ -47,5 +47,9 @@ data class PreviewUrlData(
         // Value of field "og:description"
         val description: String?,
         // Value of field "og:image"
-        val mxcUrl: String?
+        val mxcUrl: String?,
+        // Value of field "og:image:width"
+        val imageWidth: Int?,
+        // Value of field "og:image:height"
+        val imageHeight: Int?
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 1f45ac2a75..7a2511feac 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -56,7 +56,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
 ) : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 21L
+        const val SESSION_STORE_SCHEMA_VERSION = 22L
     }
 
     /**
@@ -90,6 +90,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion <= 18) migrateTo19(realm)
         if (oldVersion <= 19) migrateTo20(realm)
         if (oldVersion <= 20) migrateTo21(realm)
+        if (oldVersion <= 21) migrateTo22(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -445,4 +446,15 @@ internal class RealmSessionStoreMigration @Inject constructor(
                     }
                 }
     }
+
+    private fun migrateTo22(realm: DynamicRealm) {
+        Timber.d("Step 21 -> 22")
+
+        realm.schema.get("PreviewUrlCacheEntity")
+                ?.addField(PreviewUrlCacheEntityFields.IMAGE_WIDTH, Int::class.java)
+                ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_WIDTH, true)
+                ?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java)
+                ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true)
+    }
+
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt
index b1e0b64405..f19d70a1f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt
@@ -28,7 +28,8 @@ internal open class PreviewUrlCacheEntity(
         var title: String? = null,
         var description: String? = null,
         var mxcUrl: String? = null,
-
+        var imageWidth: Int? = null,
+        var imageHeight: Int? = null,
         var lastUpdatedTimestamp: Long = 0L
 ) : RealmObject() {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
index e707c2351c..32bcf3f7ca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
@@ -48,8 +48,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
 
     override suspend fun execute(params: GetPreviewUrlTask.Params): PreviewUrlData {
         return when (params.cacheStrategy) {
-            CacheStrategy.NoCache -> doRequest(params.url, params.timestamp)
-            is CacheStrategy.TtlCache -> doRequestWithCache(
+            CacheStrategy.NoCache       -> doRequest(params.url, params.timestamp)
+            is CacheStrategy.TtlCache   -> doRequestWithCache(
                     params.url,
                     params.timestamp,
                     params.cacheStrategy.validityDurationInMillis,
@@ -77,7 +77,9 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
                 siteName = (get("og:site_name") as? String)?.unescapeHtml(),
                 title = (get("og:title") as? String)?.unescapeHtml(),
                 description = (get("og:description") as? String)?.unescapeHtml(),
-                mxcUrl = get("og:image") as? String
+                mxcUrl = get("og:image") as? String,
+                imageHeight = (get("og:image:height") as? Double)?.toInt(),
+                imageWidth = (get("og:image:width") as? Double)?.toInt(),
         )
     }
 
@@ -114,7 +116,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
             previewUrlCacheEntity.title = data.title
             previewUrlCacheEntity.description = data.description
             previewUrlCacheEntity.mxcUrl = data.mxcUrl
-
+            previewUrlCacheEntity.imageHeight = data.imageHeight
+            previewUrlCacheEntity.imageWidth = data.imageWidth
             previewUrlCacheEntity.lastUpdatedTimestamp = Date().time
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt
index dd1a9ead26..551dc29b92 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt
@@ -27,5 +27,7 @@ internal fun PreviewUrlCacheEntity.toDomain() = PreviewUrlData(
         siteName = siteName,
         title = title,
         description = description,
-        mxcUrl = mxcUrl
+        mxcUrl = mxcUrl,
+        imageWidth = imageWidth,
+        imageHeight = imageHeight
 )
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 60fe1a4b2c..157749ab74 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -83,12 +83,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
             safePreviewUrlRetriever.addListener(attributes.informationData.eventId, previewUrlViewUpdater)
         }
         holder.previewUrlView.delegate = previewUrlCallback
-        val urlPreviewBackgroundColor = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
-            Color.TRANSPARENT
-        } else {
-            ThemeUtils.getColor(holder.view.context, R.attr.vctr_system)
-        }
-        holder.previewUrlView.setCardBackgroundColor(urlPreviewBackgroundColor)
+        holder.previewUrlView.render(attributes.informationData.messageLayout)
 
         if (useBigFont) {
             holder.messageView.textSize = 44F
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
index e41f62c373..905d420463 100755
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
@@ -17,14 +17,20 @@
 package im.vector.app.features.home.room.detail.timeline.url
 
 import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.util.AttributeSet
 import android.view.View
 import androidx.core.view.isVisible
 import com.google.android.material.card.MaterialCardView
 import im.vector.app.R
 import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.glide.GlideApp
+import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.databinding.ViewUrlPreviewBinding
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
 import im.vector.app.features.media.ImageContentRenderer
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.extensions.orFalse
@@ -37,7 +43,7 @@ class PreviewUrlView @JvmOverloads constructor(
         context: Context,
         attrs: AttributeSet? = null,
         defStyleAttr: Int = 0
-) : MaterialCardView(context, attrs, defStyleAttr), View.OnClickListener {
+) : MaterialCardView(context, attrs, defStyleAttr), View.OnClickListener, TimelineMessageLayoutRenderer {
 
     private lateinit var views: ViewUrlPreviewBinding
 
@@ -75,6 +81,22 @@ class PreviewUrlView @JvmOverloads constructor(
         }
     }
 
+    override fun render(messageLayout: TimelineMessageLayout) {
+        when (messageLayout) {
+            is TimelineMessageLayout.Default -> {
+                val backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system)
+                setCardBackgroundColor(backgroundColor)
+                val guidelineBegin = DimensionConverter(resources).dpToPx(8)
+                views.urlPreviewStartGuideline.setGuidelineBegin(guidelineBegin)
+            }
+            is TimelineMessageLayout.Bubble  -> {
+                setCardBackgroundColor(Color.TRANSPARENT)
+                rippleColor = ColorStateList.valueOf(Color.TRANSPARENT)
+                views.urlPreviewStartGuideline.setGuidelineBegin(0)
+            }
+        }
+    }
+
     override fun onClick(v: View?) {
         when (val finalState = state) {
             is PreviewUrlUiState.Data -> delegate?.onPreviewUrlClicked(finalState.url)
@@ -126,7 +148,7 @@ class PreviewUrlView @JvmOverloads constructor(
         isVisible = true
 
         views.urlPreviewTitle.setTextOrHide(previewUrlData.title)
-        views.urlPreviewImage.isVisible = previewUrlData.mxcUrl?.let { imageContentRenderer.render(it, views.urlPreviewImage) }.orFalse()
+        views.urlPreviewImage.isVisible = imageContentRenderer.render(previewUrlData, views.urlPreviewImage)
         views.urlPreviewDescription.setTextOrHide(previewUrlData.description)
         views.urlPreviewDescription.maxLines = when {
             previewUrlData.mxcUrl != null -> 2
diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
index be9d24272d..65c99362b9 100644
--- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
@@ -44,6 +44,7 @@ import im.vector.app.core.utils.DimensionConverter
 import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.media.PreviewUrlData
 import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
 import timber.log.Timber
 import java.io.File
@@ -61,6 +62,9 @@ interface AttachmentData : Parcelable {
     val allowNonMxcUrls: Boolean
 }
 
+private const val URL_PREVIEW_IMAGE_MIN_FULL_WIDTH_PX = 600
+private const val URL_PREVIEW_IMAGE_MIN_FULL_HEIGHT_PX = 315
+
 class ImageContentRenderer @Inject constructor(private val localFilesHelper: LocalFilesHelper,
                                                private val activeSessionHolder: ActiveSessionHolder,
                                                private val dimensionConverter: DimensionConverter) {
@@ -89,12 +93,20 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
     /**
      * For url preview
      */
-    fun render(mxcUrl: String, imageView: ImageView): Boolean {
+    fun render(previewUrlData: PreviewUrlData, imageView: ImageView): Boolean {
         val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
-        val imageUrl = contentUrlResolver.resolveFullSize(mxcUrl) ?: return false
-
+        val imageUrl = contentUrlResolver.resolveFullSize(previewUrlData.mxcUrl) ?: return false
+        val maxHeight = dimensionConverter.resources.getDimensionPixelSize(R.dimen.preview_url_view_image_max_height)
+        val height = previewUrlData.imageHeight ?: URL_PREVIEW_IMAGE_MIN_FULL_HEIGHT_PX
+        val width = previewUrlData.imageWidth ?: URL_PREVIEW_IMAGE_MIN_FULL_WIDTH_PX
+        if (height < URL_PREVIEW_IMAGE_MIN_FULL_HEIGHT_PX || width < URL_PREVIEW_IMAGE_MIN_FULL_WIDTH_PX) {
+            imageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
+        } else {
+            imageView.scaleType = ImageView.ScaleType.CENTER_CROP
+        }
         GlideApp.with(imageView)
                 .load(imageUrl)
+                .override(width, height.coerceAtMost(maxHeight))
                 .into(imageView)
         return true
     }
diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
index 4bb612fedf..e2a11c7926 100644
--- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
@@ -17,12 +17,11 @@
 
     <im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView
         android:id="@+id/messageUrlPreview"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
         android:layout_marginBottom="4dp"
-        android:foreground="?attr/selectableItemBackground"
         android:visibility="gone"
-        tools:visibility="gone" />
+        tools:visibility="visible" />
 
 </LinearLayout>
diff --git a/vector/src/main/res/layout/view_url_preview.xml b/vector/src/main/res/layout/view_url_preview.xml
index ff4ab1ed9a..e9774207ca 100644
--- a/vector/src/main/res/layout/view_url_preview.xml
+++ b/vector/src/main/res/layout/view_url_preview.xml
@@ -1,79 +1,102 @@
 <?xml version="1.0" encoding="utf-8"?>
 <merge 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/informationUrlPreviewContainer"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     tools:parentTag="com.google.android.material.card.MaterialCardView">
 
-    <LinearLayout
-        android:layout_width="wrap_content"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minWidth="@dimen/chat_bubble_fixed_size"
+        android:minWidth="208dp"
         android:orientation="vertical">
 
+        <!--Image dimensions will be overrode by ImageContentRenderer -->
         <ImageView
             android:id="@+id/url_preview_image"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintHeight_max="208dp"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            tools:src="@tools:sample/backgrounds/scenic" />
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/url_preview_start_guideline"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:adjustViewBounds="true"
-            android:importantForAccessibility="no"
-            android:layout_gravity="center"
-            android:maxHeight="200dp"
-            android:scaleType="fitXY"
-            tools:src="@tools:sample/backgrounds/scenic" />
+            android:orientation="vertical"
+            app:layout_constraintGuide_begin="8dp" />
 
         <TextView
             android:id="@+id/url_preview_site"
             style="@style/Widget.Vector.TextView.Caption"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
             android:layout_marginTop="6dp"
             android:layout_marginEnd="8dp"
             android:ellipsize="end"
             android:maxLines="1"
             android:singleLine="true"
+            app:layout_constraintEnd_toStartOf="@id/url_preview_close"
             android:textColor="?vctr_content_secondary"
+            app:layout_constraintStart_toStartOf="@id/url_preview_start_guideline"
+            app:layout_constraintTop_toBottomOf="@id/url_preview_image"
+            app:layout_goneMarginTop="12dp"
             tools:text="BBC News" />
 
         <TextView
             android:id="@+id/url_preview_title"
             style="@style/Widget.Vector.TextView.Body.Medium"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
             android:layout_marginTop="4dp"
-            android:layout_marginEnd="@dimen/layout_touch_size"
+            android:layout_marginEnd="8dp"
             android:ellipsize="end"
             android:maxLines="2"
             android:textColor="?vctr_content_primary"
             android:textStyle="bold"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/url_preview_close"
+            app:layout_constraintStart_toStartOf="@id/url_preview_start_guideline"
+            app:layout_constraintTop_toBottomOf="@id/url_preview_site"
+            app:layout_goneMarginTop="12dp"
             tools:text="Jo Malone denounces her former brand's John Boyega decision" />
 
         <TextView
             android:id="@+id/url_preview_description"
             style="@style/Widget.Vector.TextView.Body"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
             android:layout_marginTop="4dp"
             android:layout_marginEnd="8dp"
             android:layout_marginBottom="8dp"
             android:ellipsize="end"
             android:textColor="?vctr_content_secondary"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@id/url_preview_start_guideline"
+            app:layout_constraintTop_toBottomOf="@id/url_preview_title"
             tools:text="The British perfumer says removing actor John Boyega from his own advert was “utterly despicable”." />
 
-    </LinearLayout>
+        <ImageView
+            android:id="@+id/url_preview_close"
+            android:layout_width="@dimen/layout_touch_size"
+            android:layout_height="@dimen/layout_touch_size"
+            android:layout_gravity="top|end"
+            android:contentDescription="@string/action_close"
+            android:scaleType="center"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            android:src="@drawable/ic_close_with_circular_bg"
+            tools:ignore="MissingPrefix" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
-    <ImageView
-        android:id="@+id/url_preview_close"
-        android:layout_width="@dimen/layout_touch_size"
-        android:layout_height="@dimen/layout_touch_size"
-        android:layout_gravity="top|end"
-        android:contentDescription="@string/action_close"
-        android:scaleType="center"
-        android:src="@drawable/ic_close_with_circular_bg"
-        tools:ignore="MissingPrefix" />
 
 </merge>
\ No newline at end of file

From 713f6f7a59ed4e0aa8204f227cb7785a5a628681 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 21 Jan 2022 16:01:13 +0100
Subject: [PATCH 18/36] Timeline: Add spacing to quote

---
 .../app/features/html/EventHtmlRenderer.kt    | 13 +++--
 .../app/features/html/ParagraphHandler.kt     | 48 +++++++++++++++++++
 2 files changed, 56 insertions(+), 5 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt

diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
index 36acad8854..5fc1306b42 100644
--- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
@@ -17,9 +17,11 @@
 package im.vector.app.features.html
 
 import android.content.Context
+import android.content.res.Resources
 import android.text.Spannable
 import androidx.core.text.toSpannable
 import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.settings.VectorPreferences
 import io.noties.markwon.AbstractMarkwonPlugin
 import io.noties.markwon.Markwon
@@ -53,11 +55,11 @@ class EventHtmlRenderer @Inject constructor(
                 .usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex
                     override fun processMarkdown(markdown: String): String {
                         return markdown
-                                .replace(Regex("""<span\s+data-mx-maths="([^"]*)">.*?</span>""")) {
-                                    matchResult -> "$$" + matchResult.groupValues[1] + "$$"
+                                .replace(Regex("""<span\s+data-mx-maths="([^"]*)">.*?</span>""")) { matchResult ->
+                                    "$$" + matchResult.groupValues[1] + "$$"
                                 }
-                                .replace(Regex("""<div\s+data-mx-maths="([^"]*)">.*?</div>""")) {
-                                    matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n"
+                                .replace(Regex("""<div\s+data-mx-maths="([^"]*)">.*?</div>""")) { matchResult ->
+                                    "\n$$\n" + matchResult.groupValues[1] + "\n$$\n"
                                 }
                     }
                 })
@@ -112,11 +114,12 @@ class EventHtmlRenderer @Inject constructor(
     }
 }
 
-class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider) : HtmlPlugin.HtmlConfigure {
+class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider, private val resources: Resources) : HtmlPlugin.HtmlConfigure {
 
     override fun configureHtml(plugin: HtmlPlugin) {
         plugin
                 .addHandler(FontTagHandler())
+                .addHandler(ParagraphHandler(DimensionConverter(resources)))
                 .addHandler(MxReplyTagHandler())
                 .addHandler(SpanHandler(colorProvider))
     }
diff --git a/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt b/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt
new file mode 100644
index 0000000000..e62134ae7c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.html
+
+import android.content.res.Resources
+import im.vector.app.core.utils.DimensionConverter
+import io.noties.markwon.MarkwonVisitor
+import io.noties.markwon.SpannableBuilder
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.MarkwonHtmlRenderer
+import io.noties.markwon.html.TagHandler
+import me.gujun.android.span.style.VerticalPaddingSpan
+import org.commonmark.node.BlockQuote
+
+class ParagraphHandler(private val dimensionConverter: DimensionConverter) : TagHandler() {
+
+    override fun supportedTags() = listOf("p")
+
+    override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+        if (tag.isBlock) {
+            visitChildren(visitor, renderer, tag.asBlock)
+        }
+        val configuration = visitor.configuration()
+        val factory = configuration.spansFactory().get(BlockQuote::class.java)
+        if (factory != null) {
+            SpannableBuilder.setSpans(
+                    visitor.builder(),
+                    VerticalPaddingSpan(dimensionConverter.dpToPx(16), 0),
+                    tag.start(),
+                    tag.end()
+            )
+        }
+    }
+}

From 158026985772709cfcc5d93468bc9691cff2ff84 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 21 Jan 2022 16:01:54 +0100
Subject: [PATCH 19/36] Bubbles: move settings to timeline section

---
 vector/src/main/res/values/strings.xml                 |  2 +-
 .../src/main/res/xml/vector_settings_preferences.xml   | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 2e476f65d2..06c3b7f41e 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3705,7 +3705,7 @@
     <string name="delete_poll_dialog_title">Remove poll</string>
     <string name="delete_poll_dialog_content">Are you sure you want to remove this poll? You won\'t be able to recover it once removed.</string>
 
-    <string name="message_bubbles">Message bubbles</string>
+    <string name="message_bubbles">Show Message bubbles</string>
 
     <string name="tooltip_attachment_photo">Open camera</string>
     <string name="tooltip_attachment_gallery">Send images and videos</string>
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index a25562e3d2..080a0a6bc2 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -22,11 +22,6 @@
             android:title="@string/settings_theme"
             app:iconSpaceReserved="false" />
 
-        <im.vector.app.core.preference.VectorSwitchPreference
-            android:defaultValue="false"
-            android:key="SETTINGS_INTERFACE_BUBBLE_KEY"
-            android:title="@string/message_bubbles" />
-
         <im.vector.app.core.preference.VectorPreference
             android:dialogTitle="@string/font_size"
             android:key="SETTINGS_INTERFACE_TEXT_SIZE_KEY"
@@ -81,6 +76,11 @@
 
     <im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/settings_category_timeline">
 
+        <im.vector.app.core.preference.VectorSwitchPreference
+            android:defaultValue="false"
+            android:key="SETTINGS_INTERFACE_BUBBLE_KEY"
+            android:title="@string/message_bubbles" />
+
         <im.vector.app.core.preference.VectorSwitchPreference
             android:defaultValue="true"
             android:key="SETTINGS_SHOW_URL_PREVIEW_KEY"

From 608d8a5d54e82ee3e2402a88bf9e631a44528e3d Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Mon, 24 Jan 2022 19:31:04 +0100
Subject: [PATCH 20/36] Bubbles:  change colors

---
 .../src/main/res/values/colors_message_bubble.xml     | 11 +++++++++++
 library/ui-styles/src/main/res/values/theme_dark.xml  |  2 ++
 library/ui-styles/src/main/res/values/theme_light.xml |  2 ++
 .../room/detail/timeline/view/MessageBubbleView.kt    |  8 ++++----
 4 files changed, 19 insertions(+), 4 deletions(-)
 create mode 100644 library/ui-styles/src/main/res/values/colors_message_bubble.xml

diff --git a/library/ui-styles/src/main/res/values/colors_message_bubble.xml b/library/ui-styles/src/main/res/values/colors_message_bubble.xml
new file mode 100644
index 0000000000..7ac68574b6
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/colors_message_bubble.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!-- Timeline bubble background colors -->
+    <attr name="vctr_message_bubble_inbound" format="color" />
+    <color name="vctr_message_bubble_inbound_light">#E8EDF4</color>
+    <color name="vctr_message_bubble_inbound_dark">#21262C</color>
+    <attr name="vctr_message_bubble_outbound" format="color" />
+    <color name="vctr_message_bubble_outbound_light">#E7F8F3</color>
+    <color name="vctr_message_bubble_outbound_dark">#133A34</color>
+</resources>
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index b828855721..542b2d4bbc 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -31,6 +31,8 @@
         <item name="vctr_waiting_background_color">@color/vctr_waiting_background_color_dark</item>
         <item name="vctr_chat_effect_snow_background">@color/vctr_chat_effect_snow_background_dark</item>
         <item name="vctr_toolbar_background">@color/element_system_dark</item>
+        <item name="vctr_message_bubble_inbound">@color/vctr_message_bubble_inbound_dark</item>
+        <item name="vctr_message_bubble_outbound">@color/vctr_message_bubble_outbound_dark</item>
 
         <!-- room message colors -->
         <item name="vctr_notice_secondary">#61708B</item>
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index 790a0bfc7c..5f032481d5 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -31,6 +31,8 @@
         <item name="vctr_waiting_background_color">@color/vctr_waiting_background_color_light</item>
         <item name="vctr_chat_effect_snow_background">@color/vctr_chat_effect_snow_background_light</item>
         <item name="vctr_toolbar_background">@color/element_background_light</item>
+        <item name="vctr_message_bubble_inbound">@color/vctr_message_bubble_inbound_light</item>
+        <item name="vctr_message_bubble_outbound">@color/vctr_message_bubble_outbound_light</item>
 
         <!-- room message colors -->
         <item name="vctr_notice_secondary">#61708B</item>
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index d36e3068b7..f1250f7efe 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -139,16 +139,16 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
         val backgroundColor: Int
         if (isIncoming) {
-            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system)
+            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_message_bubble_inbound)
             shapeAppearanceModelBuilder
                     .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setTopLeftCorner(topCornerFamily, topRadius)
                     .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
         } else {
-            val resolvedColor = ContextCompat.getColor(context, R.color.palette_element_green)
-            val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26
-            backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha)
+            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_message_bubble_outbound)
+            //val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26
+            //backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha)
             shapeAppearanceModelBuilder
                     .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)

From 8f0e1039aa8516772e0c60acb666fbe74d5a7e4e Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Mon, 24 Jan 2022 19:31:51 +0100
Subject: [PATCH 21/36] Bubbles: make round style algorithm more accurate

---
 .../style/TimelineMessageLayoutFactory.kt     | 49 ++++++++++++-------
 1 file changed, 30 insertions(+), 19 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index e12e27472c..952d59e372 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -81,26 +81,25 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                 buildModernLayout(showInformation)
             }
             TimelineLayoutSettings.BUBBLE -> {
-                val type = event.root.getClearType()
-                if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
-                    val messageContent = event.getLastMessageContent()
-                    if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
-                        buildModernLayout(showInformation)
-                    } else {
-                        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
-                                prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                val shouldBuildBubbleLayout = event.shouldBuildBubbleLayout()
+                if (shouldBuildBubbleLayout) {
+                    val isFirstFromThisSender = nextDisplayableEvent == null || !nextDisplayableEvent.shouldBuildBubbleLayout() ||
+                            nextDisplayableEvent.root.senderId != event.root.senderId || addDaySeparator
 
-                        TimelineMessageLayout.Bubble(
-                                showAvatar = showInformation && !isSentByMe,
-                                showDisplayName = showInformation && !isSentByMe,
-                                isIncoming = !isSentByMe,
-                                isFirstFromThisSender = isFirstFromThisSender,
-                                isLastFromThisSender = isLastFromThisSender,
-                                isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT,
-                                timestampAsOverlay = messageContent?.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
-                        )
-                    }
+                    val isLastFromThisSender = prevDisplayableEvent == null || !prevDisplayableEvent.shouldBuildBubbleLayout() ||
+                            prevDisplayableEvent.root.senderId != event.root.senderId ||
+                            prevDisplayableEvent.root.localDateTime().toLocalDate() != date.toLocalDate()
+
+                    val messageContent = event.getLastMessageContent()
+                    TimelineMessageLayout.Bubble(
+                            showAvatar = showInformation && !isSentByMe,
+                            showDisplayName = showInformation && !isSentByMe,
+                            isIncoming = !isSentByMe,
+                            isFirstFromThisSender = isFirstFromThisSender,
+                            isLastFromThisSender = isLastFromThisSender,
+                            isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT,
+                            timestampAsOverlay = messageContent?.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
+                    )
                 } else {
                     buildModernLayout(showInformation)
                 }
@@ -109,6 +108,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         return messageLayout
     }
 
+    private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean {
+        val type = root.getClearType()
+        if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
+            val messageContent = getLastMessageContent()
+            if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
+                return false
+            }
+            return true
+        }
+        return false
+    }
+
     private fun buildModernLayout(showInformation: Boolean): TimelineMessageLayout.Default {
         return TimelineMessageLayout.Default(
                 showAvatar = showInformation,

From a9fe21e583d678041e33900167727c1a0746d990 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 27 Jan 2022 18:17:23 +0100
Subject: [PATCH 22/36] Timeline html rendering: handle code tags

---
 .../timeline/factory/MessageItemFactory.kt    | 60 ++-----------
 .../timeline/item/MessageBlockCodeItem.kt     | 57 ------------
 .../vector/app/features/html/CodeVisitor.kt   | 55 ------------
 .../app/features/html/EventHtmlRenderer.kt    |  2 +
 .../app/features/html/HtmlCodeHandlers.kt     | 60 +++++++++++++
 .../vector/app/features/html/HtmlCodeSpan.kt  | 86 +++++++++++++++++++
 .../app/features/html/ParagraphHandler.kt     | 18 ++--
 7 files changed, 161 insertions(+), 177 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
 delete mode 100644 vector/src/main/java/im/vector/app/features/html/CodeVisitor.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/html/HtmlCodeSpan.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index c004a2f928..2fc996c28d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -43,8 +43,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttrib
 import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
 import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
 import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
-import im.vector.app.features.home.room.detail.timeline.item.MessageBlockCodeItem
-import im.vector.app.features.home.room.detail.timeline.item.MessageBlockCodeItem_
 import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
 import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem_
 import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
@@ -63,7 +61,6 @@ import im.vector.app.features.home.room.detail.timeline.item.VerificationRequest
 import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem_
 import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.app.features.home.room.detail.timeline.tools.linkify
-import im.vector.app.features.html.CodeVisitor
 import im.vector.app.features.html.EventHtmlRenderer
 import im.vector.app.features.html.PillsPostProcessor
 import im.vector.app.features.html.SpanUtils
@@ -71,7 +68,6 @@ import im.vector.app.features.html.VectorHtmlCompressor
 import im.vector.app.features.media.ImageContentRenderer
 import im.vector.app.features.media.VideoContentRenderer
 import me.gujun.android.span.span
-import org.commonmark.node.Document
 import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
@@ -454,46 +450,22 @@ class MessageItemFactory @Inject constructor(
                                         highlight: Boolean,
                                         callback: TimelineEventController.Callback?,
                                         attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
-        val isFormatted = messageContent.matrixFormattedBody.isNullOrBlank().not()
-        return if (isFormatted) {
-            // First detect if the message contains some code block(s) or inline code
-            val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
-            val codeVisitor = CodeVisitor()
-            codeVisitor.visit(localFormattedBody)
-            when (codeVisitor.codeKind) {
-                CodeVisitor.Kind.BLOCK  -> {
-                    val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody)
-                    if (codeFormattedBlock == null) {
-                        buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
-                    } else {
-                        buildCodeBlockItem(codeFormattedBlock, informationData, highlight, callback, attributes)
-                    }
-                }
-                CodeVisitor.Kind.INLINE -> {
-                    val codeFormatted = htmlRenderer.get().render(localFormattedBody)
-                    if (codeFormatted == null) {
-                        buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
-                    } else {
-                        buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
-                    }
-                }
-                CodeVisitor.Kind.NONE   -> {
-                    buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
-                }
-            }
+        val matrixFormattedBody = messageContent.matrixFormattedBody
+        return if (matrixFormattedBody != null) {
+            buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
         } else {
             buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
         }
     }
 
-    private fun buildFormattedTextItem(messageContent: MessageTextContent,
+    private fun buildFormattedTextItem(matrixFormattedBody: String,
                                        informationData: MessageInformationData,
                                        highlight: Boolean,
                                        callback: TimelineEventController.Callback?,
                                        attributes: AbsMessageItem.Attributes): MessageTextItem? {
-        val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
-        val formattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor)
-        return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
+        val compressed = htmlCompressor.compress(matrixFormattedBody)
+        val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
+        return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
     }
 
     private fun buildMessageTextItem(body: CharSequence,
@@ -526,24 +498,6 @@ class MessageItemFactory @Inject constructor(
                 .movementMethod(createLinkMovementMethod(callback))
     }
 
-    private fun buildCodeBlockItem(formattedBody: CharSequence,
-                                   informationData: MessageInformationData,
-                                   highlight: Boolean,
-                                   callback: TimelineEventController.Callback?,
-                                   attributes: AbsMessageItem.Attributes): MessageBlockCodeItem? {
-        return MessageBlockCodeItem_()
-                .apply {
-                    if (informationData.hasBeenEdited) {
-                        val spannable = annotateWithEdited("", callback, informationData)
-                        editedSpan(spannable.toEpoxyCharSequence())
-                    }
-                }
-                .leftGuideline(avatarSizeProvider.leftGuideline)
-                .attributes(attributes)
-                .highlighted(highlight)
-                .message(formattedBody.toEpoxyCharSequence())
-    }
-
     private fun annotateWithEdited(linkifiedBody: CharSequence,
                                    callback: TimelineEventController.Callback?,
                                    informationData: MessageInformationData): Spannable {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
deleted file mode 100644
index 9e162a8f1e..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.detail.timeline.item
-
-import android.widget.TextView
-import com.airbnb.epoxy.EpoxyAttribute
-import com.airbnb.epoxy.EpoxyModelClass
-import im.vector.app.R
-import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
-import im.vector.app.core.epoxy.onClick
-import im.vector.app.core.extensions.setTextOrHide
-import me.saket.bettermovementmethod.BetterLinkMovementMethod
-
-@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
-abstract class MessageBlockCodeItem : AbsMessageItem<MessageBlockCodeItem.Holder>() {
-
-    @EpoxyAttribute
-    var message: EpoxyCharSequence? = null
-
-    @EpoxyAttribute
-    var editedSpan: EpoxyCharSequence? = null
-
-    override fun bind(holder: Holder) {
-        super.bind(holder)
-        holder.messageView.text = message?.charSequence
-        renderSendState(holder.messageView, holder.messageView)
-        holder.messageView.onClick(attributes.itemClickListener)
-        holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
-        holder.editedView.movementMethod = BetterLinkMovementMethod.getInstance()
-        holder.editedView.setTextOrHide(editedSpan?.charSequence)
-    }
-
-    override fun getViewStubId() = STUB_ID
-
-    class Holder : AbsMessageItem.Holder(STUB_ID) {
-        val messageView by bind<TextView>(R.id.codeBlockTextView)
-        val editedView by bind<TextView>(R.id.codeBlockEditedView)
-    }
-
-    companion object {
-        private const val STUB_ID = R.id.messageContentCodeBlockStub
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/html/CodeVisitor.kt b/vector/src/main/java/im/vector/app/features/html/CodeVisitor.kt
deleted file mode 100644
index f1612c3717..0000000000
--- a/vector/src/main/java/im/vector/app/features/html/CodeVisitor.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.html
-
-import org.commonmark.node.AbstractVisitor
-import org.commonmark.node.Code
-import org.commonmark.node.FencedCodeBlock
-import org.commonmark.node.IndentedCodeBlock
-
-/**
- * This class is in charge of visiting nodes and tells if we have some code nodes (inline or block).
- */
-class CodeVisitor : AbstractVisitor() {
-
-    var codeKind: Kind = Kind.NONE
-        private set
-
-    override fun visit(fencedCodeBlock: FencedCodeBlock?) {
-        if (codeKind == Kind.NONE) {
-            codeKind = Kind.BLOCK
-        }
-    }
-
-    override fun visit(indentedCodeBlock: IndentedCodeBlock?) {
-        if (codeKind == Kind.NONE) {
-            codeKind = Kind.BLOCK
-        }
-    }
-
-    override fun visit(code: Code?) {
-        if (codeKind == Kind.NONE) {
-            codeKind = Kind.INLINE
-        }
-    }
-
-    enum class Kind {
-        NONE,
-        INLINE,
-        BLOCK
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
index 5fc1306b42..7d78be3584 100644
--- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
@@ -121,6 +121,8 @@ class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: C
                 .addHandler(FontTagHandler())
                 .addHandler(ParagraphHandler(DimensionConverter(resources)))
                 .addHandler(MxReplyTagHandler())
+                .addHandler(CodePreTagHandler())
+                .addHandler(CodeTagHandler())
                 .addHandler(SpanHandler(colorProvider))
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt
new file mode 100644
index 0000000000..1010625370
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.html
+
+import io.noties.markwon.MarkwonVisitor
+import io.noties.markwon.SpannableBuilder
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.MarkwonHtmlRenderer
+import io.noties.markwon.html.TagHandler
+
+class CodeTagHandler : TagHandler() {
+
+    override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+        SpannableBuilder.setSpans(
+                visitor.builder(),
+                HtmlCodeSpan(visitor.configuration().theme(), false),
+                tag.start(),
+                tag.end()
+        )
+    }
+
+    override fun supportedTags(): List<String> {
+        return listOf("code")
+    }
+}
+
+/**
+ * Pre tag are already handled by HtmlPlugin to keep the formatting.
+ * We are only using it to check for <pre><code>*</code></pre> tags.
+ */
+class CodePreTagHandler : TagHandler() {
+    override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+        val htmlCodeSpan = visitor.builder()
+                .getSpans(tag.start(), tag.end())
+                .firstOrNull {
+                    it.what is HtmlCodeSpan
+                }
+        if (htmlCodeSpan != null) {
+            (htmlCodeSpan.what as HtmlCodeSpan).isBlock = true
+        }
+    }
+
+    override fun supportedTags(): List<String> {
+        return listOf("pre")
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/html/HtmlCodeSpan.kt b/vector/src/main/java/im/vector/app/features/html/HtmlCodeSpan.kt
new file mode 100644
index 0000000000..7f01321aab
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/html/HtmlCodeSpan.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.html
+
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Rect
+import android.text.Layout
+import android.text.TextPaint
+import android.text.style.LeadingMarginSpan
+import android.text.style.MetricAffectingSpan
+import io.noties.markwon.core.MarkwonTheme
+
+class HtmlCodeSpan(private val theme: MarkwonTheme, var isBlock: Boolean) : MetricAffectingSpan(), LeadingMarginSpan {
+
+    private val rect = Rect()
+    private val paint = Paint()
+
+    override fun updateDrawState(p: TextPaint) {
+        applyTextStyle(p)
+        if (!isBlock) {
+            p.bgColor = theme.getCodeBackgroundColor(p)
+        }
+    }
+
+    override fun updateMeasureState(p: TextPaint) {
+        applyTextStyle(p)
+    }
+
+    private fun applyTextStyle(p: TextPaint) {
+        if (isBlock) {
+            theme.applyCodeBlockTextStyle(p)
+        } else {
+            theme.applyCodeTextStyle(p)
+        }
+    }
+
+    override fun getLeadingMargin(first: Boolean): Int {
+        return theme.codeBlockMargin
+    }
+
+    override fun drawLeadingMargin(
+            c: Canvas,
+            p: Paint?,
+            x: Int,
+            dir: Int,
+            top: Int,
+            baseline: Int,
+            bottom: Int,
+            text: CharSequence?,
+            start: Int,
+            end: Int,
+            first: Boolean,
+            layout: Layout?
+    ) {
+        if (!isBlock) return
+
+        paint.style = Paint.Style.FILL
+        paint.color = theme.getCodeBlockBackgroundColor(p!!)
+        val left: Int
+        val right: Int
+        if (dir > 0) {
+            left = x
+            right = c.width
+        } else {
+            left = x - c.width
+            right = x
+        }
+        rect[left, top, right] = bottom
+        c.drawRect(rect, paint)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt b/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt
index e62134ae7c..3dd1b4f091 100644
--- a/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/html/ParagraphHandler.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.html
 
-import android.content.res.Resources
 import im.vector.app.core.utils.DimensionConverter
 import io.noties.markwon.MarkwonVisitor
 import io.noties.markwon.SpannableBuilder
@@ -24,7 +23,6 @@ import io.noties.markwon.html.HtmlTag
 import io.noties.markwon.html.MarkwonHtmlRenderer
 import io.noties.markwon.html.TagHandler
 import me.gujun.android.span.style.VerticalPaddingSpan
-import org.commonmark.node.BlockQuote
 
 class ParagraphHandler(private val dimensionConverter: DimensionConverter) : TagHandler() {
 
@@ -34,15 +32,11 @@ class ParagraphHandler(private val dimensionConverter: DimensionConverter) : Tag
         if (tag.isBlock) {
             visitChildren(visitor, renderer, tag.asBlock)
         }
-        val configuration = visitor.configuration()
-        val factory = configuration.spansFactory().get(BlockQuote::class.java)
-        if (factory != null) {
-            SpannableBuilder.setSpans(
-                    visitor.builder(),
-                    VerticalPaddingSpan(dimensionConverter.dpToPx(16), 0),
-                    tag.start(),
-                    tag.end()
-            )
-        }
+        SpannableBuilder.setSpans(
+                visitor.builder(),
+                VerticalPaddingSpan(dimensionConverter.dpToPx(4), dimensionConverter.dpToPx(4)),
+                tag.start(),
+                tag.end()
+        )
     }
 }

From 4de421d663f409813de6f0ec07d8b65906d9d039 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 27 Jan 2022 18:17:56 +0100
Subject: [PATCH 23/36] Timeline html rendering: better reply and pill

---
 .../app/features/html/MxReplyTagHandler.kt      | 17 +++--------------
 .../vector/app/features/html/PillImageSpan.kt   | 17 ++++++++++++-----
 2 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/html/MxReplyTagHandler.kt b/vector/src/main/java/im/vector/app/features/html/MxReplyTagHandler.kt
index 391c5f9477..118369e3c8 100644
--- a/vector/src/main/java/im/vector/app/features/html/MxReplyTagHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/html/MxReplyTagHandler.kt
@@ -17,28 +17,17 @@
 package im.vector.app.features.html
 
 import io.noties.markwon.MarkwonVisitor
-import io.noties.markwon.SpannableBuilder
 import io.noties.markwon.html.HtmlTag
 import io.noties.markwon.html.MarkwonHtmlRenderer
 import io.noties.markwon.html.TagHandler
-import org.commonmark.node.BlockQuote
 
 class MxReplyTagHandler : TagHandler() {
 
     override fun supportedTags() = listOf("mx-reply")
 
     override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
-        val configuration = visitor.configuration()
-        val factory = configuration.spansFactory().get(BlockQuote::class.java)
-        if (factory != null) {
-            SpannableBuilder.setSpans(
-                    visitor.builder(),
-                    factory.getSpans(configuration, visitor.renderProps()),
-                    tag.start(),
-                    tag.end()
-            )
-            val replyText = visitor.builder().removeFromEnd(tag.end())
-            visitor.builder().append("\n\n").append(replyText)
-        }
+        visitChildren(visitor, renderer, tag.asBlock)
+        val replyText = visitor.builder().removeFromEnd(tag.end())
+        visitor.builder().append("\n\n").append(replyText)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
index c1040a8cc0..ff2e2a9cdb 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
@@ -65,10 +65,15 @@ class PillImageSpan(private val glideRequests: GlideRequests,
                          fm: Paint.FontMetricsInt?): Int {
         val rect = pillDrawable.bounds
         if (fm != null) {
-            fm.ascent = -rect.bottom
-            fm.descent = 0
-            fm.top = fm.ascent
-            fm.bottom = 0
+            val fmPaint = paint.fontMetricsInt
+            val fontHeight = fmPaint.bottom - fmPaint.top
+            val drHeight = rect.bottom - rect.top
+            val top = drHeight / 2 - fontHeight / 4
+            val bottom = drHeight / 2 + fontHeight / 4
+            fm.ascent = -bottom
+            fm.top = -bottom
+            fm.bottom = top
+            fm.descent = top
         }
         return rect.right
     }
@@ -82,7 +87,9 @@ class PillImageSpan(private val glideRequests: GlideRequests,
                       bottom: Int,
                       paint: Paint) {
         canvas.save()
-        val transY = bottom - pillDrawable.bounds.bottom
+        val fm = paint.fontMetricsInt
+        val transY: Int = y + (fm.descent + fm.ascent - pillDrawable.bounds.bottom) / 2
+        canvas.save()
         canvas.translate(x, transY.toFloat())
         pillDrawable.draw(canvas)
         canvas.restore()

From 881b063d452b31ab74968a055355e569bf4ce818 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 27 Jan 2022 18:18:29 +0100
Subject: [PATCH 24/36] Bubbles: remove emote from bubble (keep right/left
 alignment)

---
 .../detail/timeline/style/TimelineMessageLayoutFactory.kt    | 5 ++++-
 .../home/room/detail/timeline/view/MessageBubbleView.kt      | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 952d59e372..e5ba544657 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -48,7 +48,10 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
 
         // Use the bubble layout but without borders
         private val MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT = setOf(
-                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_STICKER_LOCAL
+                MessageType.MSGTYPE_IMAGE,
+                MessageType.MSGTYPE_VIDEO,
+                MessageType.MSGTYPE_STICKER_LOCAL,
+                MessageType.MSGTYPE_EMOTE
         )
         private val MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY = setOf(
                 MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index f1250f7efe..4a21cffed6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -103,7 +103,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             }
             applyTo(views.bubbleView)
         }
-        if (messageLayout.isPseudoBubble) {
+        if (messageLayout.isPseudoBubble && messageLayout.timestampAsOverlay) {
             views.viewStubContainer.root.setPadding(0, 0, 0, 0)
         } else {
             views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)

From ed9adf83672253ff92470ddcae1fa3bea79557cd Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 27 Jan 2022 18:18:39 +0100
Subject: [PATCH 25/36] Bubbles: fix paddings

---
 .../src/main/res/values/styles_timeline.xml        |  7 +++++++
 .../res/layout/item_timeline_event_base_noinfo.xml |  1 +
 .../res/layout/item_timeline_event_base_state.xml  |  1 +
 .../item_timeline_event_view_stubs_container.xml   | 14 +-------------
 4 files changed, 10 insertions(+), 13 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index 127d9f3dc7..3bd3543de2 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -6,6 +6,13 @@
         <item name="android:layout_height">wrap_content</item>
     </style>
 
+    <style name="TimelineContentStubContainerParams">
+        <item name="android:paddingStart">8dp</item>
+        <item name="android:paddingEnd">8dp</item>
+        <item name="android:paddingTop">4dp</item>
+        <item name="android:paddingBottom">4dp</item>
+    </style>
+
     <style name="TimelineContentMediaPillStyle">
         <item name="android:paddingStart">8dp</item>
         <item name="android:paddingEnd">8dp</item>
diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
index f400fc6a9a..731642cabd 100644
--- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
@@ -26,6 +26,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
+        style="@style/TimelineContentStubContainerParams"
         android:layout_toEndOf="@id/messageStartGuideline">
 
         <ViewStub
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 ba546c040f..927abbfd16 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
@@ -31,6 +31,7 @@
 
     <FrameLayout
         android:id="@+id/viewStubContainer"
+        style="@style/TimelineContentStubContainerParams"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
index 440a1f4602..1fea380b1f 100644
--- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
+++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
@@ -4,12 +4,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:addStatesFromChildren="true"
-    android:paddingStart="8dp"
-    android:paddingLeft="8dp"
-    android:paddingTop="4dp"
-    android:paddingEnd="8dp"
-    android:paddingRight="8dp"
-    android:paddingBottom="4dp">
+    style="@style/TimelineContentStubContainerParams">
 
     <ViewStub
         android:id="@+id/messageContentTextStub"
@@ -18,13 +13,6 @@
         android:layout="@layout/item_timeline_event_text_message_stub"
         tools:visibility="visible" />
 
-    <ViewStub
-        android:id="@+id/messageContentCodeBlockStub"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout="@layout/item_timeline_event_code_block_stub"
-        tools:visibility="visible" />
-
     <ViewStub
         android:id="@+id/messageContentMediaStub"
         style="@style/TimelineContentStubBaseParams"

From b79a5fd4f4a944be2cea7581c3b75a5d5f58edbd Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 28 Jan 2022 15:09:43 +0100
Subject: [PATCH 26/36] Bubble: move overlay view to MessageBubbleView (and fix
 corner radius)

---
 .../timeline/item/MessageImageVideoItem.kt    |  2 -
 .../style/TimelineMessageLayoutExt.kt         | 48 ++++++++++++++++
 .../detail/timeline/view/MessageBubbleView.kt | 55 ++++++++-----------
 .../res/drawable/overlay_bubble_media.xml     |  1 -
 ...item_timeline_event_media_message_stub.xml | 11 ----
 .../main/res/layout/view_message_bubble.xml   | 12 ++++
 6 files changed, 82 insertions(+), 47 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 274841973e..2ac84e6ff6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -89,7 +89,6 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         holder.mediaContentView.onClick(attributes.itemClickListener)
         holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
-        holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble && messageLayout.timestampAsOverlay
     }
 
     override fun unbind(holder: Holder) {
@@ -108,7 +107,6 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         val imageView by bind<ImageView>(R.id.messageThumbnailView)
         val playContentView by bind<ImageView>(R.id.messageMediaPlayView)
         val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)
-        val overlayView by bind<View>(R.id.messageMediaOverlayView)
     }
 
     companion object {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt
new file mode 100644
index 0000000000..503c9c7a6d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+import com.google.android.material.shape.CornerFamily
+import com.google.android.material.shape.ShapeAppearanceModel
+
+fun TimelineMessageLayout.Bubble.shapeAppearanceModel(cornerRadius: Float): ShapeAppearanceModel {
+    val (topCornerFamily, topRadius) = if (isFirstFromThisSender) {
+        Pair(CornerFamily.ROUNDED, cornerRadius)
+    } else {
+        Pair(CornerFamily.CUT, 0f)
+    }
+    val (bottomCornerFamily, bottomRadius) = if (isLastFromThisSender) {
+        Pair(CornerFamily.ROUNDED, cornerRadius)
+    } else {
+        Pair(CornerFamily.CUT, 0f)
+    }
+    val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
+    if (isIncoming) {
+        shapeAppearanceModelBuilder
+                .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                .setTopLeftCorner(topCornerFamily, topRadius)
+                .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
+    } else {
+        shapeAppearanceModelBuilder
+                .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                .setTopRightCorner(topCornerFamily, topRadius)
+                .setBottomRightCorner(bottomCornerFamily, bottomRadius)
+    }
+    return shapeAppearanceModelBuilder.build()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 4a21cffed6..49771d1712 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -20,6 +20,7 @@ import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.Color
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
@@ -27,15 +28,14 @@ import android.widget.RelativeLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
-import androidx.core.graphics.ColorUtils
+import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
-import com.google.android.material.shape.CornerFamily
 import com.google.android.material.shape.MaterialShapeDrawable
-import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.databinding.ViewMessageBubbleBinding
 import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.home.room.detail.timeline.style.shapeAppearanceModel
 import im.vector.app.features.themes.ThemeUtils
 import timber.log.Timber
 
@@ -103,6 +103,12 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             }
             applyTo(views.bubbleView)
         }
+        if (messageLayout.timestampAsOverlay) {
+            views.messageOverlayView.isVisible = true
+            (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = messageLayout.cornerRadii(cornerRadius)
+        } else {
+            views.messageOverlayView.isVisible = false
+        }
         if (messageLayout.isPseudoBubble && messageLayout.timestampAsOverlay) {
             views.viewStubContainer.root.setPadding(0, 0, 0, 0)
         } else {
@@ -125,41 +131,24 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
     }
 
+    private fun TimelineMessageLayout.Bubble.cornerRadii(cornerRadius: Float): FloatArray {
+        val topRadius = if (isFirstFromThisSender) cornerRadius else 0f
+        val bottomRadius = if (isLastFromThisSender) cornerRadius else 0f
+        return if (isIncoming) {
+            floatArrayOf(topRadius, topRadius, cornerRadius, cornerRadius, cornerRadius, cornerRadius, bottomRadius, bottomRadius)
+        } else {
+            floatArrayOf(cornerRadius, cornerRadius, topRadius, topRadius, bottomRadius, bottomRadius, cornerRadius, cornerRadius)
+        }
+    }
+
     private fun createBackgroundDrawable(messageLayout: TimelineMessageLayout.Bubble): Drawable {
-        val (topCornerFamily, topRadius) = if (messageLayout.isFirstFromThisSender) {
-            Pair(CornerFamily.ROUNDED, cornerRadius)
-        } else {
-            Pair(CornerFamily.CUT, 0f)
-        }
-        val (bottomCornerFamily, bottomRadius) = if (messageLayout.isLastFromThisSender) {
-            Pair(CornerFamily.ROUNDED, cornerRadius)
-        } else {
-            Pair(CornerFamily.CUT, 0f)
-        }
-        val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
-        val backgroundColor: Int
-        if (isIncoming) {
-            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_message_bubble_inbound)
-            shapeAppearanceModelBuilder
-                    .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
-                    .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
-                    .setTopLeftCorner(topCornerFamily, topRadius)
-                    .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
-        } else {
-            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_message_bubble_outbound)
-            //val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26
-            //backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha)
-            shapeAppearanceModelBuilder
-                    .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
-                    .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
-                    .setTopRightCorner(topCornerFamily, topRadius)
-                    .setBottomRightCorner(bottomCornerFamily, bottomRadius)
-        }
-        val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
+        val shapeAppearanceModel = messageLayout.shapeAppearanceModel(cornerRadius)
         return MaterialShapeDrawable(shapeAppearanceModel).apply {
             fillColor = if (messageLayout.isPseudoBubble) {
                 ColorStateList.valueOf(Color.TRANSPARENT)
             } else {
+                val backgroundColorAttr = if (isIncoming) R.attr.vctr_message_bubble_inbound else R.attr.vctr_message_bubble_outbound
+                val backgroundColor = ThemeUtils.getColor(context, backgroundColorAttr)
                 ColorStateList.valueOf(backgroundColor)
             }
         }
diff --git a/vector/src/main/res/drawable/overlay_bubble_media.xml b/vector/src/main/res/drawable/overlay_bubble_media.xml
index 8bc9c58064..ce34a39037 100644
--- a/vector/src/main/res/drawable/overlay_bubble_media.xml
+++ b/vector/src/main/res/drawable/overlay_bubble_media.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <corners android:radius="@dimen/chat_bubble_corner_radius"/>
     <gradient
         android:type="linear"
         android:angle="270"
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index bdfa0c5164..7b3c5199b7 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -18,17 +18,6 @@
         tools:layout_height="300dp"
         tools:src="@tools:sample/backgrounds/scenic" />
 
-    <View
-        android:id="@+id/messageMediaOverlayView"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:background="@drawable/overlay_bubble_media"
-        android:elevation="2dp"
-        app:layout_constraintStart_toStartOf="@id/messageThumbnailView"
-        app:layout_constraintEnd_toEndOf="@id/messageThumbnailView"
-        app:layout_constraintTop_toTopOf="@id/messageThumbnailView"
-        app:layout_constraintBottom_toBottomOf="@id/messageThumbnailView"/>
-
     <ImageView
         android:id="@+id/messageMediaPlayView"
         android:layout_width="40dp"
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index d6263b96d7..1ffb358d4e 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -105,6 +105,18 @@
                 app:layout_constraintTop_toTopOf="parent"
                 app:layout_constraintWidth_max="@dimen/chat_bubble_fixed_size" />
 
+            <View
+                android:id="@+id/messageOverlayView"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:background="@drawable/overlay_bubble_media"
+                android:visibility="gone"
+                tools:visibility="visible"
+                app:layout_constraintStart_toStartOf="@id/viewStubContainer"
+                app:layout_constraintEnd_toEndOf="@id/viewStubContainer"
+                app:layout_constraintTop_toTopOf="@id/viewStubContainer"
+                app:layout_constraintBottom_toBottomOf="@id/viewStubContainer"/>
+
             <TextView
                 android:id="@+id/messageTimeView"
                 android:layout_width="wrap_content"

From 35674ad401189620887bdc0b3f18ae943356a07b Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 28 Jan 2022 15:10:13 +0100
Subject: [PATCH 27/36] Bubbles: handle location sharing (need updates)

---
 .../timeline/item/MessageLocationItem.kt      | 11 +++++++++-
 .../style/TimelineMessageLayoutFactory.kt     | 22 ++++++++++++++-----
 .../item_timeline_event_location_stub.xml     |  5 +++--
 3 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
index 51d95570c0..6d91ef2d14 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
@@ -20,9 +20,14 @@ import android.widget.FrameLayout
 import androidx.constraintlayout.widget.ConstraintLayout
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
+import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners
+import com.google.android.material.card.MaterialCardView
 import im.vector.app.R
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.home.room.detail.timeline.style.shapeAppearanceModel
 import im.vector.app.features.location.LocationData
 import im.vector.app.features.location.MapTilerMapView
 
@@ -48,9 +53,12 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
     override fun bind(holder: Holder) {
         super.bind(holder)
         renderSendState(holder.mapViewContainer, null)
-
         val location = locationData ?: return
         val locationOwnerId = userId ?: return
+        val messageLayout = attributes.informationData.messageLayout
+        if (messageLayout is TimelineMessageLayout.Bubble) {
+            holder.mapCardView.shapeAppearanceModel = messageLayout.shapeAppearanceModel(12f)
+        }
 
         holder.clickableMapArea.onClick {
             callback?.onMapClicked()
@@ -71,6 +79,7 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
     override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
+        val mapCardView by bind<MaterialCardView>(R.id.mapCardView)
         val mapViewContainer by bind<ConstraintLayout>(R.id.mapViewContainer)
         val mapView by bind<MapTilerMapView>(R.id.mapView)
         val clickableMapArea by bind<FrameLayout>(R.id.clickableMapArea)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index e5ba544657..2f6ec15fde 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -21,6 +21,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
 import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -100,8 +101,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                             isIncoming = !isSentByMe,
                             isFirstFromThisSender = isFirstFromThisSender,
                             isLastFromThisSender = isLastFromThisSender,
-                            isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT,
-                            timestampAsOverlay = messageContent?.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
+                            isPseudoBubble = messageContent.isPseudoBubble(),
+                            timestampAsOverlay = messageContent.timestampAsOverlay()
                     )
                 } else {
                     buildModernLayout(showInformation)
@@ -111,14 +112,23 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         return messageLayout
     }
 
+    private fun MessageContent?.isPseudoBubble(): Boolean{
+        if(this == null) return false
+        if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
+        return this.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
+    }
+
+    private fun MessageContent?.timestampAsOverlay(): Boolean{
+        if(this == null) return false
+        if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
+        return this.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
+    }
+
     private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean {
         val type = root.getClearType()
         if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
             val messageContent = getLastMessageContent()
-            if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
-                return false
-            }
-            return true
+            return messageContent?.msgType !in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT
         }
         return false
     }
diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
index b2f68b2fc3..134e9e005d 100644
--- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
@@ -1,13 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mapCardView"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:cardCornerRadius="8dp">
+    android:layout_height="wrap_content">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/mapViewContainer"
+        android:minWidth="@dimen/chat_bubble_fixed_size"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
 

From c425701c209441290feae6d38f21b88ef3b42938 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 28 Jan 2022 17:55:32 +0100
Subject: [PATCH 28/36] Bubbles: handle ripple effect

---
 .../detail/timeline/view/MessageBubbleView.kt | 30 +++++++++++++------
 ...em_timeline_event_bubble_incoming_base.xml |  1 -
 ...em_timeline_event_bubble_outgoing_base.xml |  1 -
 3 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 49771d1712..111f5c880d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -21,6 +21,7 @@ import android.content.res.ColorStateList
 import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
@@ -28,6 +29,7 @@ import android.widget.RelativeLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
+import androidx.core.graphics.drawable.DrawableCompat
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
 import com.google.android.material.shape.MaterialShapeDrawable
@@ -50,6 +52,8 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
     private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
 
     private lateinit var views: ViewMessageBubbleBinding
+    private lateinit var bubbleDrawable: MaterialShapeDrawable
+    private lateinit var rippleMaskDrawable: MaterialShapeDrawable
 
     init {
         inflate(context, R.layout.view_message_bubble, this)
@@ -72,11 +76,21 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             } else {
                 View.LAYOUT_DIRECTION_LTR
             }
-
             views.informationBottom.layoutDirection = oppositeLayoutDirection
             views.bubbleWrapper.layoutDirection = oppositeLayoutDirection
             views.bubbleView.layoutDirection = currentLayoutDirection
         }
+        bubbleDrawable = MaterialShapeDrawable()
+        rippleMaskDrawable = MaterialShapeDrawable()
+        DrawableCompat.setTint(rippleMaskDrawable, Color.WHITE)
+        views.bubbleView.apply {
+            outlineProvider = ViewOutlineProvider.BACKGROUND
+            clipToOutline = true
+            background = RippleDrawable(
+                    ContextCompat.getColorStateList(context, R.color.mtrl_btn_ripple_color) ?: ColorStateList.valueOf(Color.TRANSPARENT),
+                    bubbleDrawable,
+                    rippleMaskDrawable)
+        }
     }
 
     override fun render(messageLayout: TimelineMessageLayout) {
@@ -84,11 +98,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             Timber.v("Can't render messageLayout $messageLayout")
             return
         }
-        views.bubbleView.apply {
-            background = createBackgroundDrawable(messageLayout)
-            outlineProvider = ViewOutlineProvider.BACKGROUND
-            clipToOutline = true
-        }
+        updateDrawables(messageLayout)
         ConstraintSet().apply {
             clone(views.bubbleView)
             clear(R.id.viewStubContainer, ConstraintSet.END)
@@ -141,10 +151,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
     }
 
-    private fun createBackgroundDrawable(messageLayout: TimelineMessageLayout.Bubble): Drawable {
+    private fun updateDrawables(messageLayout: TimelineMessageLayout.Bubble) {
         val shapeAppearanceModel = messageLayout.shapeAppearanceModel(cornerRadius)
-        return MaterialShapeDrawable(shapeAppearanceModel).apply {
-            fillColor = if (messageLayout.isPseudoBubble) {
+        bubbleDrawable.apply {
+            this.shapeAppearanceModel = shapeAppearanceModel
+            this.fillColor = if (messageLayout.isPseudoBubble) {
                 ColorStateList.valueOf(Color.TRANSPARENT)
             } else {
                 val backgroundColorAttr = if (isIncoming) R.attr.vctr_message_bubble_inbound else R.attr.vctr_message_bubble_outbound
@@ -152,5 +163,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
                 ColorStateList.valueOf(backgroundColor)
             }
         }
+        rippleMaskDrawable.shapeAppearanceModel = shapeAppearanceModel
     }
 }
diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
index ac8e3aad48..5052efb46e 100644
--- a/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
@@ -4,5 +4,4 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:addStatesFromChildren="true"
-    android:background="?attr/selectableItemBackground"
     app:incoming_style="true" />
diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
index 42bf1b5f7a..0a58a53b88 100644
--- a/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
@@ -4,5 +4,4 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:addStatesFromChildren="true"
-    android:background="?attr/selectableItemBackground"
     app:incoming_style="false" />

From fd99d6d7d820c140831f67792085499a6ec0c4de Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Fri, 28 Jan 2022 19:53:07 +0100
Subject: [PATCH 29/36] Bubbles: start fixing RTL

---
 .../detail/timeline/view/MessageBubbleView.kt | 21 ++++++++++---------
 .../reactions/widget/ReactionButton.kt        |  1 +
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 111f5c880d..a843a408c9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -19,9 +19,9 @@ package im.vector.app.features.home.room.detail.timeline.view
 import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.Color
-import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.RippleDrawable
+import android.text.TextUtils
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
@@ -34,6 +34,7 @@ import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
 import com.google.android.material.shape.MaterialShapeDrawable
 import im.vector.app.R
+import im.vector.app.core.resources.LocaleProvider
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.databinding.ViewMessageBubbleBinding
 import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
@@ -65,21 +66,21 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
     override fun onFinishInflate() {
         super.onFinishInflate()
         views = ViewMessageBubbleBinding.bind(this)
-        val currentLayoutDirection = layoutDirection
-        if (isIncoming) {
-            views.informationBottom.layoutDirection = currentLayoutDirection
-            views.bubbleWrapper.layoutDirection = currentLayoutDirection
-            views.bubbleView.layoutDirection = currentLayoutDirection
+        val currentLocale = LocaleProvider(resources).current()
+        val currentLayoutDirection = TextUtils.getLayoutDirectionFromLocale(currentLocale)
+        val layoutDirectionToSet = if (isIncoming) {
+            currentLayoutDirection
         } else {
-            val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+            if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
             } else {
                 View.LAYOUT_DIRECTION_LTR
             }
-            views.informationBottom.layoutDirection = oppositeLayoutDirection
-            views.bubbleWrapper.layoutDirection = oppositeLayoutDirection
-            views.bubbleView.layoutDirection = currentLayoutDirection
         }
+        views.informationBottom.layoutDirection = layoutDirectionToSet
+        views.bubbleWrapper.layoutDirection = layoutDirectionToSet
+        views.bubbleView.layoutDirection = currentLayoutDirection
+
         bubbleDrawable = MaterialShapeDrawable()
         rippleMaskDrawable = MaterialShapeDrawable()
         DrawableCompat.setTint(rippleMaskDrawable, Color.WHITE)
diff --git a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
index 67095b974a..cc41141607 100644
--- a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
@@ -70,6 +70,7 @@ class ReactionButton @JvmOverloads constructor(context: Context,
         orientation = HORIZONTAL
         minimumHeight = DimensionConverter(context.resources).dpToPx(30)
         gravity = Gravity.CENTER
+        layoutDirection = View.LAYOUT_DIRECTION_LOCALE
         views = ReactionButtonBinding.bind(this)
         views.reactionCount.text = TextUtils.formatCountToShortDecimal(reactionCount)
         context.withStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr) {

From 820bc644b6066de5ac89aa561d492b0ff3da9988 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Mon, 31 Jan 2022 19:18:42 +0100
Subject: [PATCH 30/36] Bubble: introduce CornersRadius

---
 .../timeline/item/MessageImageVideoItem.kt    | 11 +---
 .../timeline/item/MessageLocationItem.kt      |  4 --
 .../detail/timeline/style/CornersRadius.kt    | 38 +++++++++++++
 .../timeline/style/TimelineMessageLayout.kt   | 14 ++++-
 .../style/TimelineMessageLayoutExt.kt         | 48 ----------------
 .../style/TimelineMessageLayoutFactory.kt     | 55 ++++++++++++++++---
 .../detail/timeline/view/MessageBubbleView.kt | 17 ++----
 7 files changed, 103 insertions(+), 84 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt
 delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 2ac84e6ff6..6f801d9eb7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -33,7 +33,7 @@ import im.vector.app.core.glide.GlideApp
 import im.vector.app.core.utils.DimensionConverter
 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.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
+import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
 import im.vector.app.features.media.ImageContentRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -62,14 +62,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
         val messageLayout = baseAttributes.informationData.messageLayout
         val dimensionConverter = DimensionConverter(holder.view.resources)
         val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
-            val cornerRadius = holder.view.resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
-            val topRadius = if (messageLayout.isFirstFromThisSender) cornerRadius else 0f
-            val bottomRadius = if (messageLayout.isLastFromThisSender) cornerRadius else 0f
-            if (messageLayout.isIncoming) {
-                GranularRoundedCorners(topRadius, cornerRadius, cornerRadius, bottomRadius)
-            } else {
-                GranularRoundedCorners(cornerRadius, topRadius, bottomRadius, cornerRadius)
-            }
+            messageLayout.cornersRadius.granularRoundedCorners()
         } else {
             RoundedCorners(dimensionConverter.dpToPx(8))
         }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
index 6d91ef2d14..6ed4b1bce4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
@@ -55,10 +55,6 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
         renderSendState(holder.mapViewContainer, null)
         val location = locationData ?: return
         val locationOwnerId = userId ?: return
-        val messageLayout = attributes.informationData.messageLayout
-        if (messageLayout is TimelineMessageLayout.Bubble) {
-            holder.mapCardView.shapeAppearanceModel = messageLayout.shapeAppearanceModel(12f)
-        }
 
         holder.clickableMapArea.onClick {
             callback?.onMapClicked()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt
new file mode 100644
index 0000000000..c10bebdcf4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.style
+
+import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
+import com.google.android.material.shape.CornerFamily
+import com.google.android.material.shape.ShapeAppearanceModel
+
+fun TimelineMessageLayout.Bubble.CornersRadius.granularRoundedCorners(): GranularRoundedCorners {
+    return GranularRoundedCorners(topStartRadius, topEndRadius, bottomEndRadius, bottomStartRadius)
+}
+
+fun TimelineMessageLayout.Bubble.CornersRadius.shapeAppearanceModel(): ShapeAppearanceModel {
+    return ShapeAppearanceModel().toBuilder()
+            .setTopRightCorner(topEndRadius.cornerFamily(), topEndRadius)
+            .setBottomRightCorner(bottomEndRadius.cornerFamily(), bottomEndRadius)
+            .setTopLeftCorner(topStartRadius.cornerFamily(), topStartRadius)
+            .setBottomLeftCorner(bottomStartRadius.cornerFamily(), bottomStartRadius)
+            .build()
+}
+
+private fun Float.cornerFamily(): Int {
+    return if (this == 0F) CornerFamily.CUT else CornerFamily.ROUNDED
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index bdcbc52037..c87680de0a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -39,14 +39,22 @@ sealed interface TimelineMessageLayout : Parcelable {
             override val showDisplayName: Boolean,
             override val showTimestamp: Boolean = true,
             val isIncoming: Boolean,
-            val isFirstFromThisSender: Boolean,
-            val isLastFromThisSender: Boolean,
             val isPseudoBubble: Boolean,
+            val cornersRadius: CornersRadius,
             val timestampAsOverlay: Boolean,
             override val layoutRes: Int = if (isIncoming) {
                 R.layout.item_timeline_event_bubble_incoming_base
             } else {
                 R.layout.item_timeline_event_bubble_outgoing_base
             }
-    ) : TimelineMessageLayout
+    ) : TimelineMessageLayout {
+
+        @Parcelize
+        data class CornersRadius(
+                val topStartRadius: Float,
+                val topEndRadius: Float,
+                val bottomStartRadius: Float,
+                val bottomEndRadius: Float
+        ) : Parcelable
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt
deleted file mode 100644
index 503c9c7a6d..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2022 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.detail.timeline.style
-
-import com.google.android.material.shape.CornerFamily
-import com.google.android.material.shape.ShapeAppearanceModel
-
-fun TimelineMessageLayout.Bubble.shapeAppearanceModel(cornerRadius: Float): ShapeAppearanceModel {
-    val (topCornerFamily, topRadius) = if (isFirstFromThisSender) {
-        Pair(CornerFamily.ROUNDED, cornerRadius)
-    } else {
-        Pair(CornerFamily.CUT, 0f)
-    }
-    val (bottomCornerFamily, bottomRadius) = if (isLastFromThisSender) {
-        Pair(CornerFamily.ROUNDED, cornerRadius)
-    } else {
-        Pair(CornerFamily.CUT, 0f)
-    }
-    val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
-    if (isIncoming) {
-        shapeAppearanceModelBuilder
-                .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
-                .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
-                .setTopLeftCorner(topCornerFamily, topRadius)
-                .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
-    } else {
-        shapeAppearanceModelBuilder
-                .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
-                .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
-                .setTopRightCorner(topCornerFamily, topRadius)
-                .setBottomRightCorner(bottomCornerFamily, bottomRadius)
-    }
-    return shapeAppearanceModelBuilder.build()
-}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 2f6ec15fde..373e9cfa68 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -16,7 +16,12 @@
 
 package im.vector.app.features.home.room.detail.timeline.style
 
+import android.content.res.Resources
+import android.text.TextUtils
+import android.view.View
+import im.vector.app.R
 import im.vector.app.core.extensions.localDateTime
+import im.vector.app.core.resources.LocaleProvider
 import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
 import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.session.Session
@@ -31,6 +36,8 @@ import javax.inject.Inject
 
 class TimelineMessageLayoutFactory @Inject constructor(private val session: Session,
                                                        private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
+                                                       private val localeProvider: LocaleProvider,
+                                                       private val resources: Resources,
                                                        private val vectorPreferences: VectorPreferences) {
 
     companion object {
@@ -59,6 +66,15 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         )
     }
 
+    private val cornerRadius: Float by lazy {
+        resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
+    }
+
+    private val isRTL: Boolean by lazy {
+        val currentLocale = localeProvider.current()
+        TextUtils.getLayoutDirectionFromLocale(currentLocale) == View.LAYOUT_DIRECTION_RTL
+    }
+
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
@@ -94,13 +110,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                             prevDisplayableEvent.root.senderId != event.root.senderId ||
                             prevDisplayableEvent.root.localDateTime().toLocalDate() != date.toLocalDate()
 
+                    val cornersRadius = buildCornersRadius(
+                            isIncoming = !isSentByMe,
+                            isFirstFromThisSender = isFirstFromThisSender,
+                            isLastFromThisSender = isLastFromThisSender
+                    )
+
                     val messageContent = event.getLastMessageContent()
                     TimelineMessageLayout.Bubble(
                             showAvatar = showInformation && !isSentByMe,
                             showDisplayName = showInformation && !isSentByMe,
                             isIncoming = !isSentByMe,
-                            isFirstFromThisSender = isFirstFromThisSender,
-                            isLastFromThisSender = isLastFromThisSender,
+                            cornersRadius = cornersRadius,
                             isPseudoBubble = messageContent.isPseudoBubble(),
                             timestampAsOverlay = messageContent.timestampAsOverlay()
                     )
@@ -112,15 +133,15 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         return messageLayout
     }
 
-    private fun MessageContent?.isPseudoBubble(): Boolean{
-        if(this == null) return false
-        if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
+    private fun MessageContent?.isPseudoBubble(): Boolean {
+        if (this == null) return false
+        if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
         return this.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
     }
 
-    private fun MessageContent?.timestampAsOverlay(): Boolean{
-        if(this == null) return false
-        if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
+    private fun MessageContent?.timestampAsOverlay(): Boolean {
+        if (this == null) return false
+        if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
         return this.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
     }
 
@@ -141,6 +162,24 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         )
     }
 
+    private fun buildCornersRadius(isIncoming: Boolean, isFirstFromThisSender: Boolean, isLastFromThisSender: Boolean): TimelineMessageLayout.Bubble.CornersRadius {
+        return if ((isIncoming && !isRTL) || (!isIncoming && isRTL)) {
+            TimelineMessageLayout.Bubble.CornersRadius(
+                    topStartRadius = if (isFirstFromThisSender) cornerRadius else 0f,
+                    topEndRadius = cornerRadius,
+                    bottomStartRadius = if (isLastFromThisSender) cornerRadius else 0f,
+                    bottomEndRadius = cornerRadius
+            )
+        } else {
+            TimelineMessageLayout.Bubble.CornersRadius(
+                    topStartRadius = cornerRadius,
+                    topEndRadius = if (isFirstFromThisSender) cornerRadius else 0f,
+                    bottomStartRadius = cornerRadius,
+                    bottomEndRadius = if (isLastFromThisSender) cornerRadius else 0f
+            )
+        }
+    }
+
     /**
      * Tiles type message never show the sender information (like verification request), so we should repeat it for next message
      * even if same sender
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index a843a408c9..7aea380123 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -48,7 +48,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
 
     private var isIncoming: Boolean = false
 
-    private val cornerRadius = resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
     private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12)
     private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
 
@@ -116,7 +115,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         if (messageLayout.timestampAsOverlay) {
             views.messageOverlayView.isVisible = true
-            (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = messageLayout.cornerRadii(cornerRadius)
+            (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = messageLayout.cornersRadius.toFloatArray()
         } else {
             views.messageOverlayView.isVisible = false
         }
@@ -125,7 +124,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         } else {
             views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)
         }
-        if (messageLayout.isIncoming) {
+        if (isIncoming) {
             views.messageEndGuideline.updateLayoutParams<LayoutParams> {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
             }
@@ -142,18 +141,12 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
     }
 
-    private fun TimelineMessageLayout.Bubble.cornerRadii(cornerRadius: Float): FloatArray {
-        val topRadius = if (isFirstFromThisSender) cornerRadius else 0f
-        val bottomRadius = if (isLastFromThisSender) cornerRadius else 0f
-        return if (isIncoming) {
-            floatArrayOf(topRadius, topRadius, cornerRadius, cornerRadius, cornerRadius, cornerRadius, bottomRadius, bottomRadius)
-        } else {
-            floatArrayOf(cornerRadius, cornerRadius, topRadius, topRadius, bottomRadius, bottomRadius, cornerRadius, cornerRadius)
-        }
+    private fun TimelineMessageLayout.Bubble.CornersRadius.toFloatArray(): FloatArray {
+        return floatArrayOf(topStartRadius, topStartRadius, topEndRadius, topEndRadius, bottomEndRadius, bottomEndRadius, bottomStartRadius, bottomStartRadius)
     }
 
     private fun updateDrawables(messageLayout: TimelineMessageLayout.Bubble) {
-        val shapeAppearanceModel = messageLayout.shapeAppearanceModel(cornerRadius)
+        val shapeAppearanceModel = messageLayout.cornersRadius.shapeAppearanceModel()
         bubbleDrawable.apply {
             this.shapeAppearanceModel = shapeAppearanceModel
             this.fillColor = if (messageLayout.isPseudoBubble) {

From 38f1bbdab23bc4a6e153704a17c8741c7a66730b Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Mon, 31 Jan 2022 19:20:00 +0100
Subject: [PATCH 31/36] RTL:  better support for some TextViews

---
 vector/src/main/res/layout/fragment_room_detail.xml            | 2 ++
 .../src/main/res/layout/item_bottom_sheet_message_preview.xml  | 3 +++
 vector/src/main/res/layout/item_room.xml                       | 2 ++
 vector/src/main/res/layout/item_timeline_event_base.xml        | 1 +
 vector/src/main/res/layout/item_timeline_event_base_noinfo.xml | 2 +-
 .../src/main/res/layout/item_timeline_event_default_stub.xml   | 3 +--
 vector/src/main/res/layout/item_timeline_event_notice_stub.xml | 3 +--
 vector/src/main/res/layout/view_message_bubble.xml             | 1 +
 8 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml
index 65c106d6f3..ba28fda9ea 100644
--- a/vector/src/main/res/layout/fragment_room_detail.xml
+++ b/vector/src/main/res/layout/fragment_room_detail.xml
@@ -91,6 +91,7 @@
                     android:layout_marginEnd="8dp"
                     android:ellipsize="end"
                     android:maxLines="1"
+                    android:textAlignment="viewStart"
                     android:textAppearance="@style/TextAppearance.Vector.Widget.ActionBarTitle"
                     app:layout_constraintBottom_toTopOf="@id/roomToolbarSubtitleView"
                     app:layout_constraintEnd_toEndOf="parent"
@@ -109,6 +110,7 @@
                     android:layout_marginEnd="8dp"
                     android:ellipsize="end"
                     android:maxLines="1"
+                    android:textAlignment="viewStart"
                     android:textAppearance="@style/TextAppearance.Vector.Widget.ActionBarSubTitle"
                     app:layout_constraintBottom_toBottomOf="parent"
                     app:layout_constraintEnd_toEndOf="parent"
diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml
index 95e6975803..08a8f5b27f 100644
--- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml
+++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml
@@ -35,6 +35,7 @@
         android:singleLine="true"
         android:textColor="?vctr_content_primary"
         android:textStyle="bold"
+        android:textAlignment="viewStart"
         app:layout_constraintEnd_toStartOf="@id/bottom_sheet_message_preview_timestamp"
         app:layout_constraintStart_toEndOf="@id/bottom_sheet_message_preview_avatar"
         app:layout_constraintTop_toTopOf="@id/bottom_sheet_message_preview_avatar"
@@ -78,6 +79,7 @@
         android:maxLines="3"
         android:textColor="?vctr_content_secondary"
         android:textIsSelectable="false"
+        android:textAlignment="viewStart"
         app:layout_constraintBottom_toTopOf="@id/bottom_sheet_message_preview_body_details"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toEndOf="@id/bottom_sheet_message_preview_avatar"
@@ -96,6 +98,7 @@
         android:textColor="?vctr_content_tertiary"
         android:textIsSelectable="false"
         android:visibility="gone"
+        android:textAlignment="viewStart"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/bottom_sheet_message_preview_body"
         app:layout_constraintStart_toStartOf="@id/bottom_sheet_message_preview_body"
diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index 41cc2cb02a..defff5eeeb 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -190,6 +190,7 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="3dp"
         android:layout_marginEnd="8dp"
+        android:textAlignment="viewStart"
         android:ellipsize="end"
         android:maxLines="2"
         android:textColor="?vctr_content_secondary"
@@ -202,6 +203,7 @@
         android:id="@+id/roomTypingView"
         style="@style/Widget.Vector.TextView.Body"
         android:layout_width="0dp"
+        android:textAlignment="viewStart"
         android:layout_height="wrap_content"
         android:layout_marginTop="3dp"
         android:layout_marginEnd="8dp"
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 bc9ed68232..3d0033fe59 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -34,6 +34,7 @@
         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"
diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
index 731642cabd..28a79de6bf 100644
--- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
@@ -23,10 +23,10 @@
 
     <FrameLayout
         android:id="@+id/viewStubContainer"
+        style="@style/TimelineContentStubContainerParams"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
-        style="@style/TimelineContentStubContainerParams"
         android:layout_toEndOf="@id/messageStartGuideline">
 
         <ViewStub
diff --git a/vector/src/main/res/layout/item_timeline_event_default_stub.xml b/vector/src/main/res/layout/item_timeline_event_default_stub.xml
index 000429cc68..8225f52bb8 100644
--- a/vector/src/main/res/layout/item_timeline_event_default_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_default_stub.xml
@@ -19,9 +19,8 @@
     <TextView
         android:id="@+id/itemDefaultTextView"
         style="@style/Widget.Vector.TextView.Body"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="top"
         android:layout_marginStart="8dp"
         android:layout_marginEnd="8dp"
         android:layout_marginBottom="8dp"
diff --git a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml
index e6ccdc4256..9d3a3c4098 100644
--- a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml
@@ -19,9 +19,8 @@
     <TextView
         android:id="@+id/itemNoticeTextView"
         style="@style/Widget.Vector.TextView.Body"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="top"
         android:layout_marginStart="8dp"
         android:layout_marginEnd="8dp"
         android:layout_marginBottom="8dp"
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 1ffb358d4e..6e316f7979 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -29,6 +29,7 @@
     <TextView
         android:id="@+id/messageMemberNameView"
         style="@style/Widget.Vector.TextView.Subtitle"
+        android:textAlignment="viewStart"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"

From 50810065a217403939c0340f9c32e3ec669ee612 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 1 Feb 2022 20:19:35 +0100
Subject: [PATCH 32/36] Bubbles: update media sizing (including LocationItem)

---
 .../timeline/factory/MessageItemFactory.kt    |  6 ++++-
 .../helper/TimelineMediaSizeProvider.kt       | 15 ++++++++---
 .../timeline/item/MessageLocationItem.kt      | 25 +++++++++++++++++--
 .../RoomWidgetPermissionBottomSheet.kt        |  1 -
 .../item_timeline_event_location_stub.xml     | 23 +++++++++++------
 5 files changed, 56 insertions(+), 14 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index cedcb66ef3..eda0fb925c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -25,6 +25,8 @@ import android.text.style.AbsoluteSizeSpan
 import android.text.style.ClickableSpan
 import android.text.style.ForegroundColorSpan
 import android.view.View
+import android.view.WindowManager
+import android.view.WindowMetrics
 import dagger.Lazy
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
@@ -199,7 +201,7 @@ class MessageItemFactory @Inject constructor(
                                   informationData: MessageInformationData,
                                   highlight: Boolean,
                                   attributes: AbsMessageItem.Attributes): MessageLocationItem? {
-        val width = resources.displayMetrics.widthPixels - dimensionConverter.dpToPx(60)
+        val width = timelineMediaSizeProvider.getMaxSize().first
         val height = dimensionConverter.dpToPx(200)
 
         val locationUrl = locationContent.toLocationData()?.let {
@@ -209,6 +211,8 @@ class MessageItemFactory @Inject constructor(
         return MessageLocationItem_()
                 .attributes(attributes)
                 .locationUrl(locationUrl)
+                .mapWidth(width)
+                .mapHeight(height)
                 .userId(informationData.senderId)
                 .locationPinProvider(locationPinProvider)
                 .highlighted(highlight)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt
index 9ec61e6054..53c2f6c0d4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt
@@ -16,13 +16,17 @@
 
 package im.vector.app.features.home.room.detail.timeline.helper
 
+import android.content.res.Resources
 import androidx.recyclerview.widget.RecyclerView
 import dagger.hilt.android.scopes.ActivityScoped
+import im.vector.app.R
+import im.vector.app.features.settings.VectorPreferences
 import javax.inject.Inject
 import kotlin.math.roundToInt
 
 @ActivityScoped
-class TimelineMediaSizeProvider @Inject constructor() {
+class TimelineMediaSizeProvider @Inject constructor(private val resources: Resources,
+                                                    private val vectorPreferences: VectorPreferences) {
 
     var recyclerView: RecyclerView? = null
     private var cachedSize: Pair<Int, Int>? = null
@@ -41,9 +45,14 @@ class TimelineMediaSizeProvider @Inject constructor() {
             maxImageWidth = (width * 0.7f).roundToInt()
             maxImageHeight = (height * 0.5f).roundToInt()
         } else {
-            maxImageWidth = (width * 0.5f).roundToInt()
+            maxImageWidth = (width * 0.7f).roundToInt()
             maxImageHeight = (height * 0.7f).roundToInt()
         }
-        return Pair(maxImageWidth, maxImageHeight)
+        return if (vectorPreferences.useMessageBubblesLayout()) {
+            val bubbleMaxImageWidth = maxImageWidth.coerceAtMost(resources.getDimensionPixelSize(R.dimen.chat_bubble_fixed_size))
+            Pair(bubbleMaxImageWidth, maxImageHeight)
+        } else {
+            Pair(maxImageWidth, maxImageHeight)
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
index 11faad8f13..6632b4ef30 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt
@@ -17,12 +17,16 @@
 package im.vector.app.features.home.room.detail.timeline.item
 
 import android.widget.ImageView
+import androidx.core.view.updateLayoutParams
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
-import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 import im.vector.app.R
 import im.vector.app.core.glide.GlideApp
+import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() {
@@ -33,6 +37,12 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
     @EpoxyAttribute
     var userId: String? = null
 
+    @EpoxyAttribute
+    var mapWidth: Int = 0
+
+    @EpoxyAttribute
+    var mapHeight: Int = 0
+
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
     var locationPinProvider: LocationPinProvider? = null
 
@@ -41,9 +51,20 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
         renderSendState(holder.view, null)
         val location = locationUrl ?: return
         val locationOwnerId = userId ?: return
+        val messageLayout = attributes.informationData.messageLayout
+        val dimensionConverter = DimensionConverter(holder.view.resources)
+        val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
+            messageLayout.cornersRadius.granularRoundedCorners()
+        } else {
+            RoundedCorners(dimensionConverter.dpToPx(8))
+        }
+        holder.staticMapImageView.updateLayoutParams {
+            width = mapWidth
+            height = mapHeight
+        }
         GlideApp.with(holder.staticMapImageView)
                 .load(location)
-                .apply(RequestOptions.centerCropTransform())
+                .transform(imageCornerTransformation)
                 .into(holder.staticMapImageView)
 
         locationPinProvider?.create(locationOwnerId) { pinDrawable ->
diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt
index 58cfebba94..91371b1f73 100644
--- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt
@@ -53,7 +53,6 @@ class RoomWidgetPermissionBottomSheet :
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-
         setupViews()
     }
 
diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
index 316470b5f1..77c7b932ce 100644
--- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
@@ -1,16 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:cardCornerRadius="8dp">
+    android:layout_height="wrap_content">
 
+    <!-- Size will be overrode -->
     <ImageView
         android:id="@+id/staticMapImageView"
-        android:layout_width="match_parent"
+        android:layout_width="300dp"
         android:layout_height="200dp"
-        android:contentDescription="@string/a11y_static_map_image" />
+        android:contentDescription="@string/a11y_static_map_image"
+        android:scaleType="centerCrop"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@tools:sample/backgrounds/scenic" />
 
     <ImageView
         android:id="@+id/staticMapPinImageView"
@@ -19,6 +24,10 @@
         android:layout_gravity="center"
         android:layout_marginBottom="28dp"
         android:importantForAccessibility="no"
-        android:src="@drawable/bg_map_user_pin" />
+        android:src="@drawable/bg_map_user_pin"
+        app:layout_constraintBottom_toBottomOf="@id/staticMapImageView"
+        app:layout_constraintEnd_toEndOf="@id/staticMapImageView"
+        app:layout_constraintStart_toStartOf="@id/staticMapImageView"
+        app:layout_constraintTop_toTopOf="@id/staticMapImageView" />
 
-</com.google.android.material.card.MaterialCardView>
+</androidx.constraintlayout.widget.ConstraintLayout>

From cfda76b2d4904ab2691ed29f71ac2e1e3967f162 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 1 Feb 2022 20:31:09 +0100
Subject: [PATCH 33/36] Timeline: avoid overflow on getViewType

---
 .../home/room/detail/timeline/item/BaseEventItem.kt      | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

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 5c56ad4e76..8ea761830a 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
@@ -26,8 +26,6 @@ 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.utils.DimensionConverter
-import timber.log.Timber
 
 /**
  * Children must override getViewType()
@@ -43,15 +41,14 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
 
     final override fun getViewType(): Int {
         // This makes sure we have a unique integer for the combination of layout and ViewStubId.
-        return pairingFunction(layout, getViewStubId()).also {
-            Timber.v("GetViewType: for ${javaClass.canonicalName} $it with layout:$layout and stubId:${getViewStubId()}")
-        }
+        val pairingResult = pairingFunction(layout.toLong(), getViewStubId().toLong())
+        return (pairingResult - Int.MAX_VALUE).toInt()
     }
 
     abstract fun getViewStubId(): Int
 
     // Szudzik function
-    private fun pairingFunction(a: Int, b: Int): Int {
+    private fun pairingFunction(a: Long, b: Long): Long {
         return if (a >= b) a * a + a + b else a + b * b
     }
 

From 9e0086742c2ab129aa5b2821c748448e780dc857 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Wed, 2 Feb 2022 19:56:21 +0100
Subject: [PATCH 34/36] Bubbles: clean up after review

---
 .../home/room/detail/timeline/factory/MessageItemFactory.kt | 2 --
 .../timeline/helper/ContentDownloadStateTrackerBinder.kt    | 4 +---
 .../home/room/detail/timeline/item/AbsBaseMessageItem.kt    | 2 +-
 .../home/room/detail/timeline/item/MessageFileItem.kt       | 4 ++--
 .../home/room/detail/timeline/item/MessageImageVideoItem.kt | 1 -
 .../home/room/detail/timeline/item/MessageTextItem.kt       | 2 +-
 .../home/room/detail/timeline/item/MessageVoiceItem.kt      | 5 ++---
 .../detail/timeline/style/TimelineMessageLayoutFactory.kt   | 6 ++++--
 .../home/room/detail/timeline/url/PreviewUrlView.kt         | 4 +---
 .../home/room/detail/timeline/view/MessageBubbleView.kt     | 2 +-
 .../detail/timeline/view/TimelineMessageLayoutRenderer.kt   | 2 +-
 .../im/vector/app/features/settings/VectorPreferences.kt    | 4 ++--
 vector/src/main/res/layout/view_message_bubble.xml          | 2 +-
 vector/src/main/res/layout/view_thread_room_summary.xml     | 2 +-
 14 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 51fd8d81bd..7789ae0e76 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -25,8 +25,6 @@ import android.text.style.AbsoluteSizeSpan
 import android.text.style.ClickableSpan
 import android.text.style.ForegroundColorSpan
 import android.view.View
-import android.view.WindowManager
-import android.view.WindowMetrics
 import dagger.Lazy
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
index e4405570a6..8f5f80c834 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
@@ -22,14 +22,12 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
 import dagger.hilt.android.scopes.ActivityScoped
 import im.vector.app.R
 import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.core.error.ErrorFormatter
-import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
 import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
 import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import javax.inject.Inject
 
 @ActivityScoped
-class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
+class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
 
     private val updateListeners = mutableMapOf<String, ContentDownloadUpdater>()
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index ac9ef1522d..9621b1c2f9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -99,7 +99,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
 
         holder.view.onClick(baseAttributes.itemClickListener)
         holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
-        (holder.view as? TimelineMessageLayoutRenderer)?.render(baseAttributes.informationData.messageLayout)
+        (holder.view as? TimelineMessageLayoutRenderer)?.renderMessageLayout(baseAttributes.informationData.messageLayout)
     }
 
     override fun unbind(holder: H) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
index e736c0e2da..8b6899daee 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
@@ -84,9 +84,9 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
             }
         }
 //        holder.view.setOnClickListener(clickListener)
-        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+        val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
             Color.TRANSPARENT
-        }else {
+        } else {
             ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
         }
         holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 6f801d9eb7..8485c40ef9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -23,7 +23,6 @@ import androidx.core.view.ViewCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
-import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
 import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index ed9bc87834..bc9e4a7ff1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -80,7 +80,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
             safePreviewUrlRetriever.addListener(attributes.informationData.eventId, previewUrlViewUpdater)
         }
         holder.previewUrlView.delegate = previewUrlCallback
-        holder.previewUrlView.render(attributes.informationData.messageLayout)
+        holder.previewUrlView.renderMessageLayout(attributes.informationData.messageLayout)
 
         if (useBigFont) {
             holder.messageView.textSize = 44F
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
index 1058e3c1f9..e9f728d976 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
@@ -23,7 +23,6 @@ import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageButton
 import android.widget.TextView
-import androidx.core.content.ContextCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
@@ -86,9 +85,9 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
             }
         }
 
-        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+        val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
             Color.TRANSPARENT
-        }else {
+        } else {
             ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
         }
         holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index 373e9cfa68..bded4505b5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -41,7 +41,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                                        private val vectorPreferences: VectorPreferences) {
 
     companion object {
-        // Can't be rendered in bubbles, so get back to default layout
+        // Can be rendered in bubbles, other types will fallback to default
         private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
                 EventType.MESSAGE,
                 EventType.POLL_START,
@@ -162,7 +162,9 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         )
     }
 
-    private fun buildCornersRadius(isIncoming: Boolean, isFirstFromThisSender: Boolean, isLastFromThisSender: Boolean): TimelineMessageLayout.Bubble.CornersRadius {
+    private fun buildCornersRadius(isIncoming: Boolean,
+                                   isFirstFromThisSender: Boolean,
+                                   isLastFromThisSender: Boolean): TimelineMessageLayout.Bubble.CornersRadius {
         return if ((isIncoming && !isRTL) || (!isIncoming && isRTL)) {
             TimelineMessageLayout.Bubble.CornersRadius(
                     topStartRadius = if (isFirstFromThisSender) cornerRadius else 0f,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
index 905d420463..bb306c2016 100755
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
@@ -25,7 +25,6 @@ import androidx.core.view.isVisible
 import com.google.android.material.card.MaterialCardView
 import im.vector.app.R
 import im.vector.app.core.extensions.setTextOrHide
-import im.vector.app.core.glide.GlideApp
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.databinding.ViewUrlPreviewBinding
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
@@ -33,7 +32,6 @@ import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLay
 import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
 import im.vector.app.features.media.ImageContentRenderer
 import im.vector.app.features.themes.ThemeUtils
-import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.media.PreviewUrlData
 
 /**
@@ -81,7 +79,7 @@ class PreviewUrlView @JvmOverloads constructor(
         }
     }
 
-    override fun render(messageLayout: TimelineMessageLayout) {
+    override fun renderMessageLayout(messageLayout: TimelineMessageLayout) {
         when (messageLayout) {
             is TimelineMessageLayout.Default -> {
                 val backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index 0f4db38028..de40a681b3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -94,7 +94,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
     }
 
-    override fun render(messageLayout: TimelineMessageLayout) {
+    override fun renderMessageLayout(messageLayout: TimelineMessageLayout) {
         if (messageLayout !is TimelineMessageLayout.Bubble) {
             Timber.v("Can't render messageLayout $messageLayout")
             return
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
index a339119f46..0c42662801 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
@@ -19,5 +19,5 @@ package im.vector.app.features.home.room.detail.timeline.view
 import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 
 interface TimelineMessageLayoutRenderer {
-    fun render(messageLayout: TimelineMessageLayout)
+    fun renderMessageLayout(messageLayout: TimelineMessageLayout)
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index d73b87d42c..bdf9f284b8 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -862,9 +862,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
     }
 
     /**
-     * Tells if the emoji keyboard button should be visible or not.
+     * Tells if the timeline messages should be shown in a bubble or not.
      *
-     * @return true to show emoji keyboard button.
+     * @return true to show timeline message in bubble.
      */
     fun useMessageBubblesLayout(): Boolean {
         return defaultPrefs.getBoolean(SETTINGS_INTERFACE_BUBBLE_KEY, false)
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index b86f52bf63..7e4d3e8f7d 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -200,7 +200,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/informationBottom"
         android:layout_toStartOf="@id/messageEndGuideline"
-        android:layout_toEndOf="@+id/messageStartGuideline"
+        android:layout_toEndOf="@id/messageStartGuideline"
         android:contentDescription="@string/room_threads_filter">
 
         <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml
index c326d8cbcc..0f184edef3 100644
--- a/vector/src/main/res/layout/view_thread_room_summary.xml
+++ b/vector/src/main/res/layout/view_thread_room_summary.xml
@@ -55,5 +55,5 @@
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintStart_toEndOf="@id/messageThreadSummaryAvatarImageView"
         app:layout_constraintTop_toTopOf="parent"
-        tools:text="Pouet" />
+        tools:text="@sample/messages.json/data/message" />
 </merge>

From 04234318e5bb52f3ec0e95ff87826229d41d2ba9 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 3 Feb 2022 17:53:25 +0100
Subject: [PATCH 35/36] Bubble: get LayoutDirection (RTL) from current Locale

---
 library/ui-styles/src/main/res/values-ldrtl/bools.xml       | 6 ------
 library/ui-styles/src/main/res/values/bools.xml             | 2 --
 .../java/im/vector/app/core/resources/LocaleProvider.kt     | 6 ++++++
 .../detail/timeline/style/TimelineMessageLayoutFactory.kt   | 4 ++--
 .../home/room/detail/timeline/view/MessageBubbleView.kt     | 5 ++---
 .../java/im/vector/app/features/location/UrlMapProvider.kt  | 6 ++++--
 6 files changed, 14 insertions(+), 15 deletions(-)
 delete mode 100644 library/ui-styles/src/main/res/values-ldrtl/bools.xml

diff --git a/library/ui-styles/src/main/res/values-ldrtl/bools.xml b/library/ui-styles/src/main/res/values-ldrtl/bools.xml
deleted file mode 100644
index 27b280985f..0000000000
--- a/library/ui-styles/src/main/res/values-ldrtl/bools.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
-    <bool name="is_rtl">true</bool>
-
-</resources>
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/bools.xml b/library/ui-styles/src/main/res/values/bools.xml
index 9966999f28..93d5f925af 100644
--- a/library/ui-styles/src/main/res/values/bools.xml
+++ b/library/ui-styles/src/main/res/values/bools.xml
@@ -4,6 +4,4 @@
     <!-- Created to detect what has to be implemented (especially in the settings) -->
     <bool name="false_not_implemented">false</bool>
 
-    <bool name="is_rtl">false</bool>
-
 </resources>
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
index 6a9d434aea..fdb5f21b61 100644
--- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
+++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
@@ -17,6 +17,8 @@
 package im.vector.app.core.resources
 
 import android.content.res.Resources
+import android.text.TextUtils
+import android.view.View
 import androidx.core.os.ConfigurationCompat
 import java.util.Locale
 import javax.inject.Inject
@@ -29,3 +31,7 @@ class LocaleProvider @Inject constructor(private val resources: Resources) {
 }
 
 fun LocaleProvider.isEnglishSpeaking() = current().language.startsWith("en")
+
+fun LocaleProvider.getLayoutDirectionFromCurrentLocale() =  TextUtils.getLayoutDirectionFromLocale(current())
+
+fun LocaleProvider.isRTL() = getLayoutDirectionFromCurrentLocale() == View.LAYOUT_DIRECTION_RTL
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index bded4505b5..b24ab81997 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -22,6 +22,7 @@ import android.view.View
 import im.vector.app.R
 import im.vector.app.core.extensions.localDateTime
 import im.vector.app.core.resources.LocaleProvider
+import im.vector.app.core.resources.isRTL
 import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
 import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.session.Session
@@ -71,8 +72,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
     }
 
     private val isRTL: Boolean by lazy {
-        val currentLocale = localeProvider.current()
-        TextUtils.getLayoutDirectionFromLocale(currentLocale) == View.LAYOUT_DIRECTION_RTL
+       localeProvider.isRTL()
     }
 
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
index de40a681b3..422dfb0dbd 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -21,7 +21,6 @@ import android.content.res.ColorStateList
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.RippleDrawable
-import android.text.TextUtils
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
@@ -35,6 +34,7 @@ import androidx.core.view.updateLayoutParams
 import com.google.android.material.shape.MaterialShapeDrawable
 import im.vector.app.R
 import im.vector.app.core.resources.LocaleProvider
+import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.databinding.ViewMessageBubbleBinding
 import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
@@ -65,8 +65,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
     override fun onFinishInflate() {
         super.onFinishInflate()
         views = ViewMessageBubbleBinding.bind(this)
-        val currentLocale = LocaleProvider(resources).current()
-        val currentLayoutDirection = TextUtils.getLayoutDirectionFromLocale(currentLocale)
+        val currentLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale()
         val layoutDirectionToSet = if (isIncoming) {
             currentLayoutDirection
         } else {
diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
index 76d44f5ece..8e64dc959e 100644
--- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
@@ -19,10 +19,12 @@ package im.vector.app.features.location
 import android.content.res.Resources
 import im.vector.app.BuildConfig
 import im.vector.app.R
+import im.vector.app.core.resources.LocaleProvider
+import im.vector.app.core.resources.isRTL
 import javax.inject.Inject
 
 class UrlMapProvider @Inject constructor(
-        private val resources: Resources
+        private val localeProvider: LocaleProvider
 ) {
     private val keyParam = "?key=${BuildConfig.mapTilerKey}"
 
@@ -49,7 +51,7 @@ class UrlMapProvider @Inject constructor(
             append(height)
             append(".png")
             append(keyParam)
-            if (!resources.getBoolean(R.bool.is_rtl)) {
+            if (!localeProvider.isRTL()) {
                 // On LTR languages we want the legal mentions to be displayed on the bottom left of the image
                 append("&attribution=bottomleft")
             }

From 02de6369554218dfc626a5c19d82dc0e4c89cbc2 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Thu, 3 Feb 2022 17:55:44 +0100
Subject: [PATCH 36/36] Bubbles: add CHANGELOG file

---
 changelog.d/4937.feature                                        | 1 +
 .../home/room/detail/timeline/factory/MessageItemFactory.kt     | 1 -
 .../room/detail/timeline/style/TimelineMessageLayoutFactory.kt  | 2 --
 .../main/java/im/vector/app/features/location/UrlMapProvider.kt | 2 --
 4 files changed, 1 insertion(+), 5 deletions(-)
 create mode 100644 changelog.d/4937.feature

diff --git a/changelog.d/4937.feature b/changelog.d/4937.feature
new file mode 100644
index 0000000000..8f2a3f5bc6
--- /dev/null
+++ b/changelog.d/4937.feature
@@ -0,0 +1 @@
+Support message bubbles in timeline.
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 2ad36abbde..0c836748c8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.home.room.detail.timeline.factory
 
-import android.content.res.Resources
 import android.text.Spannable
 import android.text.SpannableStringBuilder
 import android.text.Spanned
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index b24ab81997..8c1c308bb5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -17,8 +17,6 @@
 package im.vector.app.features.home.room.detail.timeline.style
 
 import android.content.res.Resources
-import android.text.TextUtils
-import android.view.View
 import im.vector.app.R
 import im.vector.app.core.extensions.localDateTime
 import im.vector.app.core.resources.LocaleProvider
diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
index 8e64dc959e..c9d9cf8112 100644
--- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
@@ -16,9 +16,7 @@
 
 package im.vector.app.features.location
 
-import android.content.res.Resources
 import im.vector.app.BuildConfig
-import im.vector.app.R
 import im.vector.app.core.resources.LocaleProvider
 import im.vector.app.core.resources.isRTL
 import javax.inject.Inject