diff --git a/changelog.d/5303.misc b/changelog.d/5303.misc
new file mode 100644
index 0000000000..dbad0b738d
--- /dev/null
+++ b/changelog.d/5303.misc
@@ -0,0 +1 @@
+Improve Bubble layouts rendering.
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt
new file mode 100644
index 0000000000..f11b1c6951
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewStub
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.marginBottom
+import androidx.core.view.marginEnd
+import androidx.core.view.marginStart
+import androidx.core.view.marginTop
+import im.vector.app.R
+import im.vector.app.core.resources.LocaleProvider
+import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale
+
+class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private var messageTextView: TextView? = null
+
+ private lateinit var contentContainerView: View
+ private lateinit var timeView: View
+ private lateinit var contentOverlayView: View
+ private var localeLayoutDirection: Int = View.LAYOUT_DIRECTION_LOCALE
+
+ private val timeViewMeasuredWidthWithMargins: Int
+ get() = timeView.measuredWidth + timeView.marginStart + timeView.marginEnd
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val textViewStub: ViewStub = findViewById(R.id.messageContentTextStub)
+ contentContainerView = findViewById(R.id.viewStubContainer)
+ contentOverlayView = findViewById(R.id.messageOverlayView)
+ timeView = findViewById(R.id.messageTimeView)
+ textViewStub.setOnInflateListener { _, inflated ->
+ textViewStub.setOnInflateListener(null)
+ messageTextView = inflated.findViewById(R.id.messageTextView)
+ }
+ localeLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ // Let the ConstraintLayouts measure children
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val messageTextView = this.messageTextView
+ // Then if we have a text message layout, we can resize ourself so we can position the timeView without losing space.
+ if (messageTextView != null) {
+ val width: Int
+ val height: Int
+ val textLineCount = messageTextView.lineCount
+ val maxContentWidth = (contentContainerView.layoutParams as LayoutParams).matchConstraintMaxWidth
+ val lastLineWidth = if (textLineCount != 0) messageTextView.layout.getLineWidth(textLineCount - 1) else 0f
+ if (textLineCount == 1 && contentContainerView.measuredWidth + timeViewMeasuredWidthWithMargins < maxContentWidth) {
+ width = contentContainerView.measuredWidth + timeViewMeasuredWidthWithMargins
+ height = contentContainerView.measuredHeight
+ } else if (textLineCount > 1 && lastLineWidth + timeViewMeasuredWidthWithMargins
+ < contentContainerView.measuredWidth - contentContainerView.paddingEnd) {
+ width = contentContainerView.measuredWidth
+ height = contentContainerView.measuredHeight
+ } else {
+ width = contentContainerView.measuredWidth
+ height = contentContainerView.measuredHeight + timeView.measuredHeight
+ }
+ setMeasuredDimension(width, height)
+ }
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ // If we have a text message layout, we want to render it manually, so we don't call super.onLayout
+ if (messageTextView != null) {
+ val parentLeft: Int = paddingLeft
+ val parentRight: Int = right - left - paddingRight
+ val parentTop: Int = paddingTop
+ val parentBottom: Int = bottom - top - paddingBottom
+ if (localeLayoutDirection == LAYOUT_DIRECTION_RTL) {
+ val contentLeft = parentRight - contentContainerView.measuredWidth - contentContainerView.marginEnd
+ contentContainerView.layout(
+ contentLeft,
+ parentTop + contentContainerView.marginTop,
+ parentRight - contentContainerView.marginEnd,
+ parentTop + contentContainerView.marginTop + contentContainerView.measuredHeight
+ )
+ timeView.layout(
+ parentLeft + timeView.marginEnd,
+ parentBottom - timeView.measuredHeight - timeView.marginBottom,
+ parentLeft + timeView.measuredWidth + timeView.marginEnd,
+ parentBottom - timeView.marginBottom
+ )
+ } else {
+ contentContainerView.layout(
+ parentLeft,
+ parentTop,
+ parentLeft + contentContainerView.measuredWidth,
+ parentTop + contentContainerView.measuredHeight
+ )
+ timeView.layout(
+ parentRight - timeView.measuredWidth - timeView.marginEnd,
+ parentBottom - timeView.measuredHeight - timeView.marginBottom,
+ parentRight - timeView.marginEnd,
+ parentBottom - timeView.marginBottom
+ )
+ }
+ } else {
+ super.onLayout(changed, left, top, right, bottom)
+ }
+ }
+}
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 422dfb0dbd..93eae9a1d3 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
@@ -49,7 +49,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
private var isIncoming: Boolean = false
private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12)
- private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
+ private val verticalStubPadding = DimensionConverter(resources).dpToPx(8)
private lateinit var views: ViewMessageBubbleBinding
private lateinit var bubbleDrawable: MaterialShapeDrawable
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 e2a11c7926..5c5280ad4e 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
@@ -12,6 +12,7 @@
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
android:textColor="?vctr_content_primary"
tools:text="@sample/messages.json/data/message" />
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index e6f24e43c5..5ae5afc329 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -84,7 +84,7 @@
android:clipChildren="false"
android:clipToPadding="false">
-
-
+