From 9d19f3c37035728c5d5af477ffdb19e6a6e1a3ed Mon Sep 17 00:00:00 2001 From: rapterjet2004 Date: Wed, 9 Sep 2026 09:43:25 -0500 Subject: [PATCH] fix(chat): stack emoji button below attachment button on multi-line messages The emoji button sitting next to the attachment button wasted about a third of the input's width once a message wrapped to multiple lines. Move it below the attachment button in that case, freeing that width for the text, while keeping the original layout for short messages. Assisted-By: Claude Code:claude-sonnet-5 Signed-off-by: rapterjet2004 --- .../talk/chat/MessageInputFragment.kt | 30 ++++++++++++ .../com/nextcloud/talk/ui/MessageInput.kt | 26 ++++++++++ .../main/res/layout/view_message_input.xml | 49 ++++++++++--------- 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index b542b984499..b1a5ad3cfad 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -15,6 +15,7 @@ import android.os.CountDownTimer import android.os.SystemClock import android.text.Editable import android.text.InputFilter +import android.text.StaticLayout import android.text.TextUtils import android.text.TextWatcher import android.util.Log @@ -41,6 +42,7 @@ import androidx.appcompat.view.ContextThemeWrapper import androidx.compose.material3.MaterialTheme import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable +import androidx.core.view.doOnLayout import androidx.core.view.isVisible import androidx.core.widget.doAfterTextChanged import androidx.emoji2.emojipicker.RecentEmojiProvider @@ -467,9 +469,12 @@ class MessageInputFragment : Fragment() { chatActivity.chatViewModel.messageDraft.messageCursor = cursor chatActivity.chatViewModel.messageDraft.messageText = text handleButtonsVisibility() + updateEmojiButtonPlacement() } }) + binding.fragmentMessageInputView.inputEditText.doOnLayout { updateEmojiButtonPlacement() } + // Image keyboard support // See: https://developer.android.com/guide/topics/text/image-keyboard @@ -720,6 +725,30 @@ class MessageInputFragment : Fragment() { } } + private fun updateEmojiButtonPlacement() { + if (!this::binding.isInitialized) { + return + } + + val editText = binding.fragmentMessageInputView.inputEditText + val smileyButton = binding.fragmentMessageInputView.smileyButton + val text = editText.text + val currentWidth = editText.width + val isCurrentlyStacked = binding.fragmentMessageInputView.isEmojiButtonStacked + val widestPossibleWidth = if (isCurrentlyStacked) currentWidth else currentWidth + smileyButton.width + + if (text == null || currentWidth <= 0 || widestPossibleWidth <= 0) { + return + } + + val lineCountAtWidestWidth = StaticLayout.Builder + .obtain(text, 0, text.length, editText.paint, widestPossibleWidth) + .build() + .lineCount + + binding.fragmentMessageInputView.setEmojiButtonStacked(lineCountAtWidestWidth >= LONG_MESSAGE_LINE_THRESHOLD) + } + fun updateScheduledMessagesAvailability(hasMessages: Boolean) { hasScheduledMessages = hasMessages handleButtonsVisibility() @@ -1280,5 +1309,6 @@ class MessageInputFragment : Fragment() { private const val FULLY_OPAQUE: Float = 1.0f private const val FULLY_TRANSPARENT: Float = 0.0f private const val OPACITY_DISABLED = 0.7f + private const val LONG_MESSAGE_LINE_THRESHOLD = 2 } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index eaad8c81d0c..392fab59ad3 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -13,6 +13,8 @@ import android.widget.Chronometer import android.widget.FrameLayout import android.widget.ImageButton import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.TextView import androidx.emoji2.widget.EmojiEditText @@ -38,11 +40,15 @@ class MessageInput : FrameLayout { lateinit var inputEditText: EmojiEditText lateinit var attachmentButton: ImageButton lateinit var messageSendButton: ImageButton + private lateinit var attachmentEmojiContainer: LinearLayout val messageInput: EmojiEditText get() = inputEditText val button: ImageButton get() = messageSendButton + var isEmojiButtonStacked = false + private set + constructor(context: Context) : super(context) { init() } @@ -64,6 +70,7 @@ class MessageInput : FrameLayout { inputEditText.maxHeight = screenHeight / 3 attachmentButton = findViewById(R.id.attachmentButton) + attachmentEmojiContainer = findViewById(R.id.attachmentEmojiContainer) messageSendButton = findViewById(R.id.messageSendButton) audioRecordDuration = findViewById(R.id.audioRecordDuration) recordAudioButton = findViewById(R.id.recordAudioButton) @@ -86,4 +93,23 @@ class MessageInput : FrameLayout { listener?.invoke() } } + + fun setEmojiButtonStacked(stacked: Boolean) { + if (isEmojiButtonStacked == stacked) { + return + } + isEmojiButtonStacked = stacked + + attachmentEmojiContainer.orientation = if (stacked) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL + + val containerParams = attachmentEmojiContainer.layoutParams as RelativeLayout.LayoutParams + if (stacked) { + containerParams.removeRule(RelativeLayout.ALIGN_BOTTOM) + containerParams.addRule(RelativeLayout.CENTER_VERTICAL) + } else { + containerParams.removeRule(RelativeLayout.CENTER_VERTICAL) + containerParams.addRule(RelativeLayout.ALIGN_BOTTOM, inputEditText.id) + } + attachmentEmojiContainer.layoutParams = containerParams + } } diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 34f2fb6dd42..7b10aecc90b 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -33,28 +33,33 @@ android:layout_height="wrap_content" android:layoutDirection="ltr"> - + android:orientation="horizontal"> - + + + +