From 75d45a079b0b5c3cdaf40022c577526c1a9d2d69 Mon Sep 17 00:00:00 2001 From: rapterjet2004 Date: Wed, 2 Sep 2026 09:58:24 -0500 Subject: [PATCH] =?UTF-8?q?-=20view=5Fmessage=5Finput.xml=20=E2=80=94=20sm?= =?UTF-8?q?ileyButton=20ImageButton=20reinstated=20between=20the=20attachm?= =?UTF-8?q?ent=20button=20and=20the=20message=20input=20(exact=20historic?= =?UTF-8?q?=20position/styling,=20using=20the=20pre-existing=20ic=5Finsert?= =?UTF-8?q?=5Femoticon=5Fblack=5F24dp=20drawable).=20-=20fragment=5Fmessag?= =?UTF-8?q?e=5Finput.xml=20=E2=80=94=20EmojiPickerView=20added=20below=20t?= =?UTF-8?q?he=20input=20row,=20250dp=20tall,=20hidden=20by=20default.=20-?= =?UTF-8?q?=20MessageInput.kt=20=E2=80=94=20exposes=20smileyButton=20again?= =?UTF-8?q?.=20-=20MessageInputFragment.kt=20=E2=80=94=20initSmileyKeyboar?= =?UTF-8?q?dToggler()=20restored:=20toggles=20between=20keyboard=20and=20e?= =?UTF-8?q?moji=20picker,=20swaps=20the=20button=20icon=20(emoji=20?= =?UTF-8?q?=E2=86=94=20keyboard=20glyph),=20inserts=20picked=20emoji=20at?= =?UTF-8?q?=20the=20cursor,=20and=20a=20MessageInputRecentEmojiProvider=20?= =?UTF-8?q?persists=20the=20last=2020=20used=20emoji=20in=20SharedPreferen?= =?UTF-8?q?ces.=20-=20TalkSpecificViewThemeUtils.kt=20=E2=80=94=20themeEmo?= =?UTF-8?q?jiPicker()=20restored,=20applying=20dynamic-color=20theming=20t?= =?UTF-8?q?o=20the=20picker=20background=20and=20category=20tabs=20via=20t?= =?UTF-8?q?he=20existing=20EmojiPickerCategoryTabs.kt=20helpers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: rapterjet2004 --- .../talk/chat/MessageInputFragment.kt | 94 +++++++++++++++++++ .../com/nextcloud/talk/ui/MessageInput.kt | 2 + .../ui/theme/TalkSpecificViewThemeUtils.kt | 13 +++ .../res/layout/fragment_message_input.xml | 6 ++ .../main/res/layout/view_message_input.xml | 14 ++- 5 files changed, 128 insertions(+), 1 deletion(-) 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 816a9d760d7..b542b984499 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -7,6 +7,7 @@ package com.nextcloud.talk.chat +import android.content.Context import android.content.res.Resources import android.os.Build import android.os.Bundle @@ -28,6 +29,7 @@ import android.view.animation.AlphaAnimation import android.view.animation.Animation import android.view.animation.Animation.AnimationListener import android.view.animation.LinearInterpolator +import android.view.inputmethod.InputMethodManager import android.widget.Chronometer import android.widget.ImageButton import android.widget.ImageView @@ -37,9 +39,11 @@ import android.widget.RelativeLayout import android.widget.SeekBar 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.isVisible import androidx.core.widget.doAfterTextChanged +import androidx.emoji2.emojipicker.RecentEmojiProvider import androidx.emoji2.widget.EmojiTextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -117,6 +121,7 @@ class MessageInputFragment : Fragment() { private var typedWhileTypingTimerIsRunning: Boolean = false private var typingTimer: CountDownTimer? = null private lateinit var chatActivity: ChatActivity + private var restoreKeyboardOnEmojiDismiss = false private var mentionAutocomplete: Autocomplete<*>? = null private var xcounter = 0f private var ycounter = 0f @@ -147,6 +152,7 @@ class MessageInputFragment : Fragment() { } override fun onDestroyView() { + restoreKeyboardOnEmojiDismiss = false super.onDestroyView() if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) { mentionAutocomplete?.dismissPopup() @@ -179,6 +185,7 @@ class MessageInputFragment : Fragment() { when (state) { is ChatViewModel.GetCapabilitiesUpdateState -> { initMessageInputView(state.spreedCapabilities) + initSmileyKeyboardToggler() setupMentionAutocomplete() initVoiceRecordButton() initThreadHandling() @@ -188,6 +195,7 @@ class MessageInputFragment : Fragment() { is ChatViewModel.GetCapabilitiesInitialLoadState -> { initMessageInputView(state.spreedCapabilities) + initSmileyKeyboardToggler() setupMentionAutocomplete() initVoiceRecordButton() initThreadHandling() @@ -773,6 +781,8 @@ class MessageInputFragment : Fragment() { private fun showRecordAudioUi(show: Boolean) { if (show) { + restoreKeyboardOnEmojiDismiss = false + binding.emojiPicker.isVisible = false val animation: Animation = AlphaAnimation(FULLY_OPAQUE, FULLY_TRANSPARENT) animation.duration = ANIMATION_DURATION animation.interpolator = LinearInterpolator() @@ -785,6 +795,7 @@ class MessageInputFragment : Fragment() { binding.fragmentMessageInputView.audioRecordDuration.visibility = View.VISIBLE binding.fragmentMessageInputView.slideToCancelDescription.visibility = View.VISIBLE binding.fragmentMessageInputView.attachmentButton.visibility = View.GONE + binding.fragmentMessageInputView.smileyButton.visibility = View.GONE binding.fragmentMessageInputView.messageInput.visibility = View.GONE binding.fragmentMessageInputView.messageInput.hint = "" binding.fragmentMessageInputView.scheduledMessagesButton.visibility = View.GONE @@ -796,12 +807,95 @@ class MessageInputFragment : Fragment() { binding.fragmentMessageInputView.audioRecordDuration.visibility = View.GONE binding.fragmentMessageInputView.slideToCancelDescription.visibility = View.GONE binding.fragmentMessageInputView.attachmentButton.visibility = View.VISIBLE + binding.fragmentMessageInputView.smileyButton.visibility = View.VISIBLE binding.fragmentMessageInputView.messageInput.visibility = View.VISIBLE binding.fragmentMessageInputView.messageInput.hint = requireContext().resources?.getString(R.string.nc_hint_enter_a_message) } } + private fun updateSmileyButtonIcon() { + val icon = if (binding.emojiPicker.isVisible) { + R.drawable.ic_baseline_keyboard_24 + } else { + R.drawable.ic_insert_emoticon_black_24dp + } + val drawable = ContextCompat.getDrawable(requireContext(), icon) + binding.fragmentMessageInputView.smileyButton.setImageDrawable(drawable) + } + + private fun showKeyboard() { + val editText = binding.fragmentMessageInputView.inputEditText + editText.requestFocus() + val inputMethodManager = + requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + inputMethodManager?.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + } + + private fun hideKeyboard() { + val view = activity?.currentFocus + if (view != null) { + val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(view.windowToken, 0) + } + } + + private fun initSmileyKeyboardToggler() { + val inputEditText = binding.fragmentMessageInputView.inputEditText + val emojiPicker = binding.emojiPicker + + emojiPicker.setRecentEmojiProvider(MessageInputRecentEmojiProvider(requireContext())) + emojiPicker.setOnEmojiPickedListener { item -> + val start = inputEditText.selectionStart + val end = inputEditText.selectionEnd + inputEditText.editableText.replace(start, end, item.emoji) + } + + viewThemeUtils.talk.themeEmojiPicker(emojiPicker) + updateSmileyButtonIcon() + + binding.fragmentMessageInputView.smileyButton.setOnClickListener { + if (emojiPicker.isVisible) { + showKeyboard() + emojiPicker.isVisible = false + } else { + hideKeyboard() + emojiPicker.isVisible = true + } + updateSmileyButtonIcon() + } + + inputEditText.setOnClickListener { + if (emojiPicker.isVisible) { + emojiPicker.isVisible = false + updateSmileyButtonIcon() + } + } + } + + private class MessageInputRecentEmojiProvider(context: Context) : RecentEmojiProvider { + private val prefs = context.getSharedPreferences("recent_emojis", Context.MODE_PRIVATE) + + override fun recordSelection(emoji: String) { + val updated = listOf(emoji) + getStoredList().filterNot { it == emoji } + prefs.edit() + .putString("recent", updated.take(MAX_STORED_RECENT_EMOJIS).joinToString(",")) + .apply() + } + + override suspend fun getRecentEmojiList(): List = getStoredList() + + private fun getStoredList(): List = + prefs.getString("recent", null) + ?.split(",") + ?.filter { it.isNotBlank() } + ?: emptyList() + + companion object { + private const val MAX_STORED_RECENT_EMOJIS = 20 + } + } + private fun replyToMessage(quotedMessageText: String?, quotedActorDisplayName: String?, quotedImageUrl: String?) { Log.d(TAG, "Reply") val view = binding.fragmentMessageInputView 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 5c30b385284..eaad8c81d0c 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -26,6 +26,7 @@ class MessageInput : FrameLayout { lateinit var slideToCancelDescription: TextView lateinit var microphoneEnabledInfo: ImageView lateinit var microphoneEnabledInfoBackground: ImageView + lateinit var smileyButton: ImageButton lateinit var deleteVoiceRecording: ImageView lateinit var sendVoiceRecording: ImageView lateinit var micInputCloud: MicInputCloud @@ -70,6 +71,7 @@ class MessageInput : FrameLayout { slideToCancelDescription = findViewById(R.id.slideToCancelDescription) microphoneEnabledInfo = findViewById(R.id.microphoneEnabledInfo) microphoneEnabledInfoBackground = findViewById(R.id.microphoneEnabledInfoBackground) + smileyButton = findViewById(R.id.smileyButton) deleteVoiceRecording = findViewById(R.id.deleteVoiceRecording) sendVoiceRecording = findViewById(R.id.sendVoiceRecording) micInputCloud = findViewById(R.id.micInputCloud) diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/TalkSpecificViewThemeUtils.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/TalkSpecificViewThemeUtils.kt index 54c8334078a..edcda2624f8 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/theme/TalkSpecificViewThemeUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/TalkSpecificViewThemeUtils.kt @@ -35,6 +35,7 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.ViewCompat +import androidx.emoji2.emojipicker.EmojiPickerView import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip @@ -492,6 +493,18 @@ class TalkSpecificViewThemeUtils @Inject constructor( } } + fun themeEmojiPicker(emojiPickerView: EmojiPickerView) { + withScheme(emojiPickerView.context) { scheme -> + emojiPickerView.setBackgroundColor(dynamicColor.surfaceContainerLow().getArgb(scheme)) + themeEmojiPickerCategoryTabs( + emojiPickerView, + dynamicColor.primary().getArgb(scheme), + dynamicColor.onSurfaceVariant().getArgb(scheme) + ) + protectEmojiPickerScrollGesture(emojiPickerView) + } + } + companion object { private val THEMEABLE_PLACEHOLDER_IDS = listOf( R.drawable.ic_mimetype_package_x_generic, diff --git a/app/src/main/res/layout/fragment_message_input.xml b/app/src/main/res/layout/fragment_message_input.xml index 189eb5f845c..aec5fc67534 100644 --- a/app/src/main/res/layout/fragment_message_input.xml +++ b/app/src/main/res/layout/fragment_message_input.xml @@ -81,6 +81,12 @@ android:textColor="@color/medium_emphasis_text" android:visibility="gone" tools:visibility="visible" /> + + diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 1aa188e06e6..34f2fb6dd42 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -44,13 +44,25 @@ android:src="@drawable/ic_baseline_attach_file_24" app:tint="?attr/colorControlNormal" /> + +