diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
index 136dbfb371..d44efa9c37 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java
@@ -32,6 +32,7 @@ import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.ViewKt;
import androidx.core.widget.TextViewCompat;
+import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
@@ -73,6 +74,7 @@ public class EmojiTextView extends AppCompatTextView {
private boolean isJumbomoji;
private boolean forceJumboEmoji;
private boolean renderSpoilers;
+ private boolean shrinkWrap;
private MentionRendererDelegate mentionRendererDelegate;
private SpoilerRendererDelegate spoilerRendererDelegate;
@@ -96,6 +98,7 @@ public class EmojiTextView extends AppCompatTextView {
measureLastLine = a.getBoolean(R.styleable.EmojiTextView_measureLastLine, false);
forceJumboEmoji = a.getBoolean(R.styleable.EmojiTextView_emoji_forceJumbo, false);
renderSpoilers = a.getBoolean(R.styleable.EmojiTextView_emoji_renderSpoilers, false);
+ shrinkWrap = a.getBoolean(R.styleable.EmojiTextView_emoji_shrinkWrap, false);
a.recycle();
a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.textSize });
@@ -224,6 +227,25 @@ public class EmojiTextView extends AppCompatTextView {
widthMeasureSpec = applyWidthMeasureRoundingFix(widthMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ if (shrinkWrap && getLayout() != null && mode == MeasureSpec.AT_MOST) {
+ Layout layout = getLayout();
+
+ float maxLineWidth = 0f;
+ for (int i = 0; i < layout.getLineCount(); i++) {
+ if (layout.getLineWidth(i) > maxLineWidth) {
+ maxLineWidth = layout.getLineWidth(i);
+ }
+ }
+
+ int desiredWidth = (int) maxLineWidth + getPaddingLeft() + getPaddingRight();
+ if (getMeasuredWidth() > desiredWidth) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, mode);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
CharSequence text = getText();
if (getLayout() == null || !measureLastLine || text == null || text.length() == 0) {
lastLineWidth = -1;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ShrinkWrapLinearLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ShrinkWrapLinearLayout.kt
new file mode 100644
index 0000000000..b51192149f
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ShrinkWrapLinearLayout.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.conversation.v2.items
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.LinearLayoutCompat
+
+/**
+ * Custom LinearLayoutCompat that will intercept EXACTLY measure-specs and
+ * overwrite them with AT_MOST. This guarantees that wrap_content is respected
+ * when the Layout is within a constraintlayout.
+ */
+class ShrinkWrapLinearLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : LinearLayoutCompat(context, attrs) {
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val shrinkWrapWidthSpec = shrinkWrapWidthMeasureSpec(widthMeasureSpec)
+ super.onMeasure(shrinkWrapWidthSpec, heightMeasureSpec)
+ }
+
+ private fun shrinkWrapWidthMeasureSpec(widthMeasureSpec: Int): Int {
+ val mode = MeasureSpec.getMode(widthMeasureSpec)
+ val size = MeasureSpec.getSize(widthMeasureSpec)
+
+ return if (mode == MeasureSpec.EXACTLY) {
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)
+ } else {
+ widthMeasureSpec
+ }
+ }
+}
diff --git a/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml b/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml
index 431e8e9d79..01af7deb45 100644
--- a/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml
+++ b/app/src/main/res/layout/v2_conversation_item_text_only_incoming.xml
@@ -56,7 +56,7 @@
tools:visibility="gone" />
-
-
+
+ app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
+ app:layout_constraintTop_toTopOf="@id/conversation_item_footer_date" />
diff --git a/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml b/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml
index 68e7f61335..38e4379106 100644
--- a/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml
+++ b/app/src/main/res/layout/v2_conversation_item_text_only_outgoing.xml
@@ -60,6 +60,7 @@
app:emoji_maxLength="1000"
app:emoji_renderMentions="true"
app:emoji_renderSpoilers="true"
+ app:emoji_shrinkWrap="true"
app:measureLastLine="true"
app:scaleEmojis="true"
tools:text="Mango pickle lorem ipsum" />
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 069a557d54..afd7a354f3 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -153,6 +153,7 @@
+