77
88package com.nextcloud.talk.chat
99
10+ import android.content.Context
1011import android.content.res.Resources
1112import android.os.Build
1213import android.os.Bundle
@@ -28,6 +29,7 @@ import android.view.animation.AlphaAnimation
2829import android.view.animation.Animation
2930import android.view.animation.Animation.AnimationListener
3031import android.view.animation.LinearInterpolator
32+ import android.view.inputmethod.InputMethodManager
3133import android.widget.Chronometer
3234import android.widget.ImageButton
3335import android.widget.ImageView
@@ -37,9 +39,11 @@ import android.widget.RelativeLayout
3739import android.widget.SeekBar
3840import androidx.appcompat.view.ContextThemeWrapper
3941import androidx.compose.material3.MaterialTheme
42+ import androidx.core.content.ContextCompat
4043import androidx.core.graphics.drawable.toDrawable
4144import androidx.core.view.isVisible
4245import androidx.core.widget.doAfterTextChanged
46+ import androidx.emoji2.emojipicker.RecentEmojiProvider
4347import androidx.emoji2.widget.EmojiTextView
4448import androidx.fragment.app.Fragment
4549import androidx.fragment.app.activityViewModels
@@ -117,6 +121,7 @@ class MessageInputFragment : Fragment() {
117121 private var typedWhileTypingTimerIsRunning: Boolean = false
118122 private var typingTimer: CountDownTimer ? = null
119123 private lateinit var chatActivity: ChatActivity
124+ private var restoreKeyboardOnEmojiDismiss = false
120125 private var mentionAutocomplete: Autocomplete <* >? = null
121126 private var xcounter = 0f
122127 private var ycounter = 0f
@@ -147,6 +152,7 @@ class MessageInputFragment : Fragment() {
147152 }
148153
149154 override fun onDestroyView () {
155+ restoreKeyboardOnEmojiDismiss = false
150156 super .onDestroyView()
151157 if (mentionAutocomplete != null && mentionAutocomplete!! .isPopupShowing) {
152158 mentionAutocomplete?.dismissPopup()
@@ -179,6 +185,7 @@ class MessageInputFragment : Fragment() {
179185 when (state) {
180186 is ChatViewModel .GetCapabilitiesUpdateState -> {
181187 initMessageInputView(state.spreedCapabilities)
188+ initSmileyKeyboardToggler()
182189 setupMentionAutocomplete()
183190 initVoiceRecordButton()
184191 initThreadHandling()
@@ -188,6 +195,7 @@ class MessageInputFragment : Fragment() {
188195
189196 is ChatViewModel .GetCapabilitiesInitialLoadState -> {
190197 initMessageInputView(state.spreedCapabilities)
198+ initSmileyKeyboardToggler()
191199 setupMentionAutocomplete()
192200 initVoiceRecordButton()
193201 initThreadHandling()
@@ -773,6 +781,8 @@ class MessageInputFragment : Fragment() {
773781
774782 private fun showRecordAudioUi (show : Boolean ) {
775783 if (show) {
784+ restoreKeyboardOnEmojiDismiss = false
785+ binding.emojiPicker.isVisible = false
776786 val animation: Animation = AlphaAnimation (FULLY_OPAQUE , FULLY_TRANSPARENT )
777787 animation.duration = ANIMATION_DURATION
778788 animation.interpolator = LinearInterpolator ()
@@ -785,6 +795,7 @@ class MessageInputFragment : Fragment() {
785795 binding.fragmentMessageInputView.audioRecordDuration.visibility = View .VISIBLE
786796 binding.fragmentMessageInputView.slideToCancelDescription.visibility = View .VISIBLE
787797 binding.fragmentMessageInputView.attachmentButton.visibility = View .GONE
798+ binding.fragmentMessageInputView.smileyButton.visibility = View .GONE
788799 binding.fragmentMessageInputView.messageInput.visibility = View .GONE
789800 binding.fragmentMessageInputView.messageInput.hint = " "
790801 binding.fragmentMessageInputView.scheduledMessagesButton.visibility = View .GONE
@@ -796,12 +807,95 @@ class MessageInputFragment : Fragment() {
796807 binding.fragmentMessageInputView.audioRecordDuration.visibility = View .GONE
797808 binding.fragmentMessageInputView.slideToCancelDescription.visibility = View .GONE
798809 binding.fragmentMessageInputView.attachmentButton.visibility = View .VISIBLE
810+ binding.fragmentMessageInputView.smileyButton.visibility = View .VISIBLE
799811 binding.fragmentMessageInputView.messageInput.visibility = View .VISIBLE
800812 binding.fragmentMessageInputView.messageInput.hint =
801813 requireContext().resources?.getString(R .string.nc_hint_enter_a_message)
802814 }
803815 }
804816
817+ private fun updateSmileyButtonIcon () {
818+ val icon = if (binding.emojiPicker.isVisible) {
819+ R .drawable.ic_baseline_keyboard_24
820+ } else {
821+ R .drawable.ic_insert_emoticon_black_24dp
822+ }
823+ val drawable = ContextCompat .getDrawable(requireContext(), icon)
824+ binding.fragmentMessageInputView.smileyButton.setImageDrawable(drawable)
825+ }
826+
827+ private fun showKeyboard () {
828+ val editText = binding.fragmentMessageInputView.inputEditText
829+ editText.requestFocus()
830+ val inputMethodManager =
831+ requireContext().getSystemService(Context .INPUT_METHOD_SERVICE ) as ? InputMethodManager
832+ inputMethodManager?.showSoftInput(editText, InputMethodManager .SHOW_IMPLICIT )
833+ }
834+
835+ private fun hideKeyboard () {
836+ val view = activity?.currentFocus
837+ if (view != null ) {
838+ val imm = requireContext().getSystemService(Context .INPUT_METHOD_SERVICE ) as ? InputMethodManager
839+ imm?.hideSoftInputFromWindow(view.windowToken, 0 )
840+ }
841+ }
842+
843+ private fun initSmileyKeyboardToggler () {
844+ val inputEditText = binding.fragmentMessageInputView.inputEditText
845+ val emojiPicker = binding.emojiPicker
846+
847+ emojiPicker.setRecentEmojiProvider(MessageInputRecentEmojiProvider (requireContext()))
848+ emojiPicker.setOnEmojiPickedListener { item ->
849+ val start = inputEditText.selectionStart
850+ val end = inputEditText.selectionEnd
851+ inputEditText.editableText.replace(start, end, item.emoji)
852+ }
853+
854+ viewThemeUtils.talk.themeEmojiPicker(emojiPicker)
855+ updateSmileyButtonIcon()
856+
857+ binding.fragmentMessageInputView.smileyButton.setOnClickListener {
858+ if (emojiPicker.isVisible) {
859+ showKeyboard()
860+ emojiPicker.isVisible = false
861+ } else {
862+ hideKeyboard()
863+ emojiPicker.isVisible = true
864+ }
865+ updateSmileyButtonIcon()
866+ }
867+
868+ inputEditText.setOnClickListener {
869+ if (emojiPicker.isVisible) {
870+ emojiPicker.isVisible = false
871+ updateSmileyButtonIcon()
872+ }
873+ }
874+ }
875+
876+ private class MessageInputRecentEmojiProvider (context : Context ) : RecentEmojiProvider {
877+ private val prefs = context.getSharedPreferences(" recent_emojis" , Context .MODE_PRIVATE )
878+
879+ override fun recordSelection (emoji : String ) {
880+ val updated = listOf (emoji) + getStoredList().filterNot { it == emoji }
881+ prefs.edit()
882+ .putString(" recent" , updated.take(MAX_STORED_RECENT_EMOJIS ).joinToString(" ," ))
883+ .apply ()
884+ }
885+
886+ override suspend fun getRecentEmojiList (): List <String > = getStoredList()
887+
888+ private fun getStoredList (): List <String > =
889+ prefs.getString(" recent" , null )
890+ ?.split(" ," )
891+ ?.filter { it.isNotBlank() }
892+ ? : emptyList()
893+
894+ companion object {
895+ private const val MAX_STORED_RECENT_EMOJIS = 20
896+ }
897+ }
898+
805899 private fun replyToMessage (quotedMessageText : String? , quotedActorDisplayName : String? , quotedImageUrl : String? ) {
806900 Log .d(TAG , " Reply" )
807901 val view = binding.fragmentMessageInputView
0 commit comments