Commit 18800044 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Keyboard backpressing closes also the emoji keyboard

parent 2e563131
...@@ -6,6 +6,7 @@ import android.os.Bundle ...@@ -6,6 +6,7 @@ import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.widget.emoji.EmojiFragment import chat.rocket.android.widget.emoji.EmojiFragment
...@@ -63,10 +64,12 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -63,10 +64,12 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
} }
override fun onBackPressed() { override fun onBackPressed() {
super.onBackPressed()
val frag = supportFragmentManager.findFragmentByTag(EmojiFragment.TAG) as EmojiFragment? val frag = supportFragmentManager.findFragmentByTag(EmojiFragment.TAG) as EmojiFragment?
if (frag != null && frag.isShown()) { if (frag != null && frag.isShown()) {
frag.hide() frag.hide()
} else { } else {
KeyboardHelper.hideSoftKeyboard(this)
finishActivity() finishActivity()
} }
} }
......
...@@ -8,13 +8,13 @@ import android.content.Intent ...@@ -8,13 +8,13 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.view.* import android.view.*
import android.widget.ImageButton
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.presentation.ChatRoomView
...@@ -23,9 +23,11 @@ import chat.rocket.android.helper.EndlessRecyclerViewScrollListener ...@@ -23,9 +23,11 @@ import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.emoji.ComposerEditText
import chat.rocket.android.widget.emoji.Emoji import chat.rocket.android.widget.emoji.Emoji
import chat.rocket.android.widget.emoji.EmojiFragment import chat.rocket.android.widget.emoji.EmojiFragment
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.common.util.ifNull
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_room.* import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
...@@ -49,7 +51,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" ...@@ -49,7 +51,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only" private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 42 private const val REQUEST_CODE_FOR_PERFORM_SAF = 42
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCallback { class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.EmojiKeyboardListener {
@Inject lateinit var presenter: ChatRoomPresenter @Inject lateinit var presenter: ChatRoomPresenter
@Inject lateinit var parser: MessageParser @Inject lateinit var parser: MessageParser
private lateinit var adapter: ChatRoomAdapter private lateinit var adapter: ChatRoomAdapter
...@@ -95,12 +97,24 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -95,12 +97,24 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
setupActionSnackbar() setupActionSnackbar()
} }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
attachOrGetEmojiFragment()
text_message.addTextChangedListener(EmojiFragment.EmojiTextWatcher(text_message))
text_message.requestFocus()
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.unsubscribeMessages() presenter.unsubscribeMessages()
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
super.onDestroyView() super.onDestroyView()
} }
override fun onStop() {
super.onStop()
hideAllKeyboards()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF && resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF && resultCode == Activity.RESULT_OK) {
if (resultData != null) { if (resultData != null) {
...@@ -134,6 +148,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -134,6 +148,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter) adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
linearLayoutManager.stackFromEnd = true
recycler_view.layoutManager = linearLayoutManager recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
if (dataSet.size >= 30) { if (dataSet.size >= 30) {
...@@ -145,6 +160,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -145,6 +160,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
} }
} }
adapter.addDataSet(dataSet) adapter.addDataSet(dataSet)
if (adapter.itemCount > 0) {
recycler_view.scrollToPosition(0)
}
} }
} }
...@@ -162,7 +180,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -162,7 +180,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
override fun showInvalidFileMessage() = showMessage(getString(R.string.msg_invalid_file)) override fun showInvalidFileMessage() = showMessage(getString(R.string.msg_invalid_file))
override fun showNewMessage(message: MessageViewModel) { override fun showNewMessage(message: MessageViewModel) {
text_message.textContent = ""
adapter.addItem(message) adapter.addItem(message)
recycler_view.smoothScrollToPosition(0) recycler_view.smoothScrollToPosition(0)
} }
...@@ -175,7 +192,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -175,7 +192,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
override fun enableMessageInput(clear: Boolean) { override fun enableMessageInput(clear: Boolean) {
button_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
if (clear) text_message.textContent = "" if (clear) text_message.erase()
} }
override fun dispatchUpdateMessage(index: Int, message: MessageViewModel) { override fun dispatchUpdateMessage(index: Int, message: MessageViewModel) {
...@@ -220,13 +237,45 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -220,13 +237,45 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
text_message.textContent = text text_message.textContent = text
editingMessageId = messageId editingMessageId = messageId
} }
} }
override fun onEmojiAdded(emoji: Emoji) { override fun onEmojiAdded(emoji: Emoji) {
val cursorPosition = text_message.selectionStart val cursorPosition = text_message.selectionStart
text_message.text.insert(cursorPosition, EmojiParser.parse(emoji.shortname)) if (cursorPosition > -1) {
text_message.setSelection(cursorPosition + emoji.unicode.length) text_message.text.insert(cursorPosition, EmojiParser.parse(emoji.shortname))
text_message.setSelection(cursorPosition + emoji.unicode.length)
}
}
override fun onSoftKeyboardHidden() {
setReactionButtonIcon(R.drawable.ic_keyboard_black_24dp)
}
override fun onSoftKeyboardShown() {
setReactionButtonIcon(R.drawable.ic_reaction_24dp)
recycler_view.scrollToPosition(0)
}
override fun onEmojiKeyboardHidden() {
setReactionButtonIcon(R.drawable.ic_reaction_24dp)
}
override fun onEmojiKeyboardShown() {
setReactionButtonIcon(R.drawable.ic_keyboard_black_24dp)
recycler_view.scrollToPosition(0)
}
private fun setReactionButtonIcon(@DrawableRes drawableId: Int) {
button_add_reaction.setImageResource(drawableId)
button_add_reaction.setTag(drawableId)
}
private fun hideAllKeyboards() {
activity?.let {
KeyboardHelper.hideSoftKeyboard(it)
attachOrGetEmojiFragment()?.hide()
setReactionButtonIcon(R.drawable.ic_reaction_24dp)
}
} }
private fun setupComposer() { private fun setupComposer() {
...@@ -251,10 +300,28 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -251,10 +300,28 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
} }
}) })
text_message.listener = object : ComposerEditText.ComposerEditTextListener {
override fun onKeyboardClose() {
activity?.let {
val fragment = EmojiFragment.getOrAttach(it, R.id.emoji_fragment_placeholder, composer)
if (fragment.isCollapsed()) {
it.onBackPressed()
} else {
hideAllKeyboards()
}
}
}
}
button_send.setOnClickListener { button_send.setOnClickListener {
var textMessage = citation ?: "" var textMessage = citation ?: ""
textMessage += text_message.textContent textMessage += text_message.textContent
sendMessage(textMessage) sendMessage(textMessage)
attachOrGetEmojiFragment()?.let {
if (it.softKeyboardVisible) {
it.hide()
}
}
clearActionMessage() clearActionMessage()
} }
...@@ -263,6 +330,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -263,6 +330,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
if (layout_message_attachment_options.isShown) { if (layout_message_attachment_options.isShown) {
hideAttachmentOptions() hideAttachmentOptions()
} else { } else {
hideAllKeyboards()
showAttachmentOptions() showAttachmentOptions()
} }
} }
...@@ -282,28 +350,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -282,28 +350,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
button_add_reaction.setOnClickListener { view -> button_add_reaction.setOnClickListener { view ->
activity?.let { activity?.let {
val editor = text_message val editor = text_message
val emojiFragment = EmojiFragment.getOrAttach(it, R.id.emoji_fragment_placeholder, composer) val emojiFragment = attachOrGetEmojiFragment()!!
with(emojiFragment) { val tag = if (view.tag == null) R.drawable.ic_reaction_24dp else view.tag as Int
if (!isShown()) { when (tag) {
show() R.drawable.ic_reaction_24dp -> {
} else { KeyboardHelper.hideSoftKeyboard(it)
val button = view as ImageButton if (!emojiFragment.isShown()) {
val resourceId: Int emojiFragment.show()
if (softKeyboardVisible) {
resourceId = R.drawable.ic_keyboard_black_24px
KeyboardHelper.hideSoftKeyboard(it)
} else {
resourceId = R.drawable.ic_reaction_24dp
KeyboardHelper.showSoftKeyboard(editor)
} }
button.setImageResource(resourceId)
} }
R.drawable.ic_keyboard_black_24dp -> KeyboardHelper.showSoftKeyboard(editor)
} }
} }
} }
}
}
addEmojiFragment() private fun attachOrGetEmojiFragment(): EmojiFragment? {
text_message.addTextChangedListener(EmojiFragment.EmojiTextWatcher(text_message)) return activity?.let {
val frag = EmojiFragment.getOrAttach(it, R.id.emoji_fragment_placeholder, composer)
frag.listener = this
frag
} }
} }
...@@ -314,12 +381,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal ...@@ -314,12 +381,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiFragment.OnEmojiClickCal
}) })
} }
private fun addEmojiFragment() {
activity?.let {
EmojiFragment.getOrAttach(it, R.id.emoji_fragment_placeholder, composer)
}
}
private fun clearActionMessage() { private fun clearActionMessage() {
citation = null citation = null
editingMessageId = null editingMessageId = null
......
...@@ -51,4 +51,11 @@ object KeyboardHelper { ...@@ -51,4 +51,11 @@ object KeyboardHelper {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
} }
} }
fun restart(view: View) {
if (view.requestFocus()) {
val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.restartInput(view)
}
}
} }
\ No newline at end of file
...@@ -10,7 +10,9 @@ import android.provider.MediaStore ...@@ -10,7 +10,9 @@ import android.provider.MediaStore
import android.text.Spannable import android.text.Spannable
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.widget.EditText
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
fun String.ifEmpty(value: String): String { fun String.ifEmpty(value: String): String {
...@@ -27,6 +29,14 @@ fun CharSequence.ifEmpty(value: String): CharSequence { ...@@ -27,6 +29,14 @@ fun CharSequence.ifEmpty(value: String): CharSequence {
return this return this
} }
fun EditText.erase() {
this.text.clear()
val spans = this.text.getSpans(0, text.length, EmojiTypefaceSpan::class.java)
spans.forEach {
text.removeSpan(it)
}
}
var TextView.textContent: String var TextView.textContent: String
get() = text.toString() get() = text.toString()
set(value) { set(value) {
...@@ -46,7 +56,8 @@ var TextView.content: CharSequence ...@@ -46,7 +56,8 @@ var TextView.content: CharSequence
Markwon.unscheduleTableRows(this) Markwon.unscheduleTableRows(this)
if (value is Spanned) { if (value is Spanned) {
val result = EmojiParser.parse(value.toString()) as Spannable val result = EmojiParser.parse(value.toString()) as Spannable
TextUtils.copySpansFrom(value, 0, value.length, Any::class.java, result, 0) val end = if (value.length > result.length) result.length else value.length
TextUtils.copySpansFrom(value, 0, end, Any::class.java, result, 0)
text = result text = result
} else { } else {
val result = EmojiParser.parse(value.toString()) as Spannable val result = EmojiParser.parse(value.toString()) as Spannable
......
...@@ -9,10 +9,10 @@ import android.view.View ...@@ -9,10 +9,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.widget.emoji.EmojiFragment.OnEmojiClickCallback import chat.rocket.android.widget.emoji.EmojiFragment.EmojiKeyboardListener
import java.util.* import java.util.*
class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter() { class CategoryPagerAdapter(val listener: EmojiKeyboardListener) : PagerAdapter() {
override fun isViewFromObject(view: View, obj: Any): Boolean { override fun isViewFromObject(view: View, obj: Any): Boolean {
return view == obj return view == obj
} }
...@@ -20,9 +20,9 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter() ...@@ -20,9 +20,9 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter()
override fun instantiateItem(container: ViewGroup, position: Int): Any { override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = LayoutInflater.from(container.context) val view = LayoutInflater.from(container.context)
.inflate(R.layout.emoji_category_layout, container, false) .inflate(R.layout.emoji_category_layout, container, false)
val layoutManager = GridLayoutManager(view.context, 5) val layoutManager = GridLayoutManager(view.context, 8)
val recycler = view.findViewById(R.id.emojiRecyclerView) as RecyclerView val recycler = view.findViewById(R.id.emojiRecyclerView) as RecyclerView
val adapter = EmojiAdapter(layoutManager.spanCount, callback) val adapter = EmojiAdapter(layoutManager.spanCount, listener)
val category = EmojiCategory.values().get(position) val category = EmojiCategory.values().get(position)
val emojis = if (category != EmojiCategory.RECENTS) val emojis = if (category != EmojiCategory.RECENTS)
EmojiRepository.getEmojisByCategory(category) EmojiRepository.getEmojisByCategory(category)
...@@ -46,7 +46,7 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter() ...@@ -46,7 +46,7 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter()
override fun getPageTitle(position: Int) = EmojiCategory.values()[position].icon() override fun getPageTitle(position: Int) = EmojiCategory.values()[position].icon()
class EmojiAdapter(val spanCount: Int, val callback: OnEmojiClickCallback) : RecyclerView.Adapter<EmojiRowViewHolder>() { class EmojiAdapter(val spanCount: Int, val listener: EmojiKeyboardListener) : RecyclerView.Adapter<EmojiRowViewHolder>() {
private var emojis: List<Emoji> = Collections.emptyList() private var emojis: List<Emoji> = Collections.emptyList()
fun addEmojis(emojis: List<Emoji>) { fun addEmojis(emojis: List<Emoji>) {
...@@ -60,13 +60,13 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter() ...@@ -60,13 +60,13 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiRowViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiRowViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.emoji_row_item, parent, false) val view = LayoutInflater.from(parent.context).inflate(R.layout.emoji_row_item, parent, false)
return EmojiRowViewHolder(view, itemCount, spanCount, callback) return EmojiRowViewHolder(view, itemCount, spanCount, listener)
} }
override fun getItemCount(): Int = emojis.size override fun getItemCount(): Int = emojis.size
} }
class EmojiRowViewHolder(itemView: View, val itemCount: Int, val spanCount: Int, val callback: OnEmojiClickCallback) : RecyclerView.ViewHolder(itemView) { class EmojiRowViewHolder(itemView: View, val itemCount: Int, val spanCount: Int, val listener: EmojiKeyboardListener) : RecyclerView.ViewHolder(itemView) {
private val emojiView: TextView = itemView.findViewById(R.id.emoji) private val emojiView: TextView = itemView.findViewById(R.id.emoji)
fun bind(emoji: Emoji) { fun bind(emoji: Emoji) {
...@@ -79,7 +79,7 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter() ...@@ -79,7 +79,7 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter()
itemView.setPadding(0, 0, 0, paddingBottom) itemView.setPadding(0, 0, 0, paddingBottom)
} }
itemView.setOnClickListener { itemView.setOnClickListener {
callback.onEmojiAdded(emoji) listener.onEmojiAdded(emoji)
} }
} }
} }
......
package chat.rocket.android.widget.emoji
import android.content.Context
import android.support.v7.widget.AppCompatEditText
import android.util.AttributeSet
import android.view.KeyEvent
class ComposerEditText : AppCompatEditText {
var listener: ComposerEditTextListener? = null
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle)
constructor(context: Context) : this(context, null)
override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
val state = getKeyDispatcherState()
if (state != null) {
if (event.action == KeyEvent.ACTION_DOWN) {
state.startTracking(event, this)
listener?.onKeyboardClose()
}
return true
}
}
return super.dispatchKeyEventPreIme(event)
}
interface ComposerEditTextListener {
fun onKeyboardClose()
}
}
\ No newline at end of file
...@@ -22,13 +22,16 @@ import chat.rocket.android.util.extensions.setVisible ...@@ -22,13 +22,16 @@ import chat.rocket.android.util.extensions.setVisible
class EmojiFragment : Fragment() { class EmojiFragment : Fragment() {
private lateinit var viewPager: ViewPager private lateinit var viewPager: ViewPager
private lateinit var tabLayout: TabLayout private lateinit var tabLayout: TabLayout
private lateinit var editor: View private lateinit var parentContainer: ViewGroup
internal lateinit var parentContainer: ViewGroup private var editor: View? = null
private var decorLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
var softKeyboardVisible = false var softKeyboardVisible = false
var listener: EmojiKeyboardListener? = null
companion object { companion object {
const val PREF_EMOJI_RECENTS = "PREF_EMOJI_RECENTS" const val PREF_EMOJI_RECENTS = "PREF_EMOJI_RECENTS"
const val PREF_KEYBOARD_HEIGHT = "PREF_KEYBOARD_HEIGHT" const val PREF_KEYBOARD_HEIGHT = "PREF_KEYBOARD_HEIGHT"
const val MIN_KEYBOARD_HEIGHT_PX = 150
val TAG: String = EmojiFragment::class.java.simpleName val TAG: String = EmojiFragment::class.java.simpleName
fun newInstance(editor: View) = EmojiFragment().apply { this.editor = editor } fun newInstance(editor: View) = EmojiFragment().apply { this.editor = editor }
...@@ -57,40 +60,51 @@ class EmojiFragment : Fragment() { ...@@ -57,40 +60,51 @@ class EmojiFragment : Fragment() {
return view return view
} }
override fun onDetach() {
super.onDetach()
activity?.getWindow()?.decorView?.viewTreeObserver?.removeOnGlobalLayoutListener(decorLayoutListener)
listener = null
editor = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val callback = when (activity) { val callback = when (activity) {
is OnEmojiClickCallback -> activity as OnEmojiClickCallback is EmojiKeyboardListener -> activity as EmojiKeyboardListener
else -> { else -> {
val fragments = activity?.supportFragmentManager?.fragments val fragments = activity?.supportFragmentManager?.fragments
if (fragments == null || fragments.size == 0 || !(fragments[0] is OnEmojiClickCallback)) { if (fragments == null || fragments.size == 0 || !(fragments[0] is EmojiKeyboardListener)) {
throw IllegalStateException("activity/fragment should implement OnEmojiClickCallback interface") throw IllegalStateException("activity/fragment should implement EmojiKeyboardListener interface")
} }
fragments[0] as OnEmojiClickCallback fragments[0] as EmojiKeyboardListener
} }
} }
activity?.let { activity?.let {
val decorView = it.getWindow().decorView val decorView = it.getWindow().decorView
decorView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { decorLayoutListener = object : ViewTreeObserver.OnGlobalLayoutListener {
private val windowVisibleDisplayFrame = Rect() private val windowVisibleDisplayFrame = Rect()
private var lastVisibleDecorViewHeight: Int = 0 private var lastVisibleDecorViewHeight: Int = 0
override fun onGlobalLayout() { override fun onGlobalLayout() {
if (editor == null) {
return
}
// Retrieve visible rectangle inside window. // Retrieve visible rectangle inside window.
decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame) decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame)
val visibleDecorViewHeight = windowVisibleDisplayFrame.height() val visibleDecorViewHeight = windowVisibleDisplayFrame.height()
// Decide whether keyboard is visible from changing decor view height. // Decide whether keyboard is visible from changing decor view height.
if (lastVisibleDecorViewHeight != 0) { if (lastVisibleDecorViewHeight != 0) {
if (lastVisibleDecorViewHeight > visibleDecorViewHeight + 150) { if (lastVisibleDecorViewHeight > visibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX) {
// Calculate current keyboard height (this includes also navigation bar height when in fullscreen mode). // Calculate current keyboard height (this includes also navigation bar height when in fullscreen mode).
val currentKeyboardHeight = decorView.height - windowVisibleDisplayFrame.bottom - editor.measuredHeight val currentKeyboardHeight = decorView.height - windowVisibleDisplayFrame.bottom - editor!!.measuredHeight
// Notify listener about keyboard being shown. // Notify listener about keyboard being shown.
EmojiRepository.saveKeyboardHeight(currentKeyboardHeight) EmojiRepository.saveKeyboardHeight(currentKeyboardHeight)
setKeyboardHeight(currentKeyboardHeight) setKeyboardHeight(currentKeyboardHeight)
softKeyboardVisible = true softKeyboardVisible = true
show() openHidden()
} else if (lastVisibleDecorViewHeight + 150 < visibleDecorViewHeight) { listener?.onSoftKeyboardShown()
} else if (lastVisibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX < visibleDecorViewHeight) {
// Notify listener about keyboard being hidden. // Notify listener about keyboard being hidden.
softKeyboardVisible = false softKeyboardVisible = false
} }
...@@ -98,7 +112,8 @@ class EmojiFragment : Fragment() { ...@@ -98,7 +112,8 @@ class EmojiFragment : Fragment() {
// Save current decor view height for the next call. // Save current decor view height for the next call.
lastVisibleDecorViewHeight = visibleDecorViewHeight lastVisibleDecorViewHeight = visibleDecorViewHeight
} }
}) }
decorView.viewTreeObserver.addOnGlobalLayoutListener(decorLayoutListener)
} }
val storedHeight = EmojiRepository.getKeyboardHeight() val storedHeight = EmojiRepository.getKeyboardHeight()
...@@ -106,7 +121,23 @@ class EmojiFragment : Fragment() { ...@@ -106,7 +121,23 @@ class EmojiFragment : Fragment() {
setKeyboardHeight(storedHeight) setKeyboardHeight(storedHeight)
} }
viewPager.adapter = CategoryPagerAdapter(object : OnEmojiClickCallback { viewPager.adapter = CategoryPagerAdapter(object : EmojiKeyboardListener {
override fun onEmojiKeyboardShown() {
// Do nothing here.
}
override fun onEmojiKeyboardHidden() {
// Do nothing here.
}
override fun onSoftKeyboardHidden() {
// Do nothing here.
}
override fun onSoftKeyboardShown() {
// Do nothing here.
}
override fun onEmojiAdded(emoji: Emoji) { override fun onEmojiAdded(emoji: Emoji) {
EmojiRepository.addToRecents(emoji) EmojiRepository.addToRecents(emoji)
callback.onEmojiAdded(emoji) callback.onEmojiAdded(emoji)
...@@ -127,8 +158,11 @@ class EmojiFragment : Fragment() { ...@@ -127,8 +158,11 @@ class EmojiFragment : Fragment() {
} }
private fun setKeyboardHeight(height: Int) { private fun setKeyboardHeight(height: Int) {
parentContainer.layoutParams.height = height val oldHeight = parentContainer.layoutParams.height
parentContainer.requestLayout() if (oldHeight != height) {
parentContainer.layoutParams.height = height
parentContainer.requestLayout()
}
} }
class EmojiTextWatcher(val editor: EditText) : TextWatcher { class EmojiTextWatcher(val editor: EditText) : TextWatcher {
...@@ -175,22 +209,57 @@ class EmojiFragment : Fragment() { ...@@ -175,22 +209,57 @@ class EmojiFragment : Fragment() {
} }
} }
/**
* Show the emoji keyboard.
*/
fun show() { fun show() {
parentContainer.setVisible(true) parentContainer.setVisible(true)
listener?.onEmojiKeyboardShown()
}
fun openHidden() {
parentContainer.visibility = View.INVISIBLE
} }
/**
* Hide the emoji keyboard.
*/
fun hide() { fun hide() {
// Since the emoji keyboard is always behind the soft keyboard assume it's also dismissed
// when the emoji one is about to get close. Hence we should invoke our listener to update
// the UI as if the soft keyboard is hidden.
parentContainer.setVisible(false) parentContainer.setVisible(false)
listener?.onEmojiKeyboardHidden()
} }
/**
* Whether the emoji keyboard is visible.
*
* @return <code>true</code> if opened.
*/
fun isShown() = parentContainer.visibility == View.VISIBLE fun isShown() = parentContainer.visibility == View.VISIBLE
interface OnEmojiClickCallback { /**
* Whether the emoji keyboard is collapsed.
*
* @return false if the emoji keyboard is visible and not obscured
*/
fun isCollapsed() = parentContainer.visibility == View.GONE
interface EmojiKeyboardListener {
/** /**
* Callback triggered after an emoji is selected on the picker. * Callback after an emoji is selected on the picker.
* *
* @param emoji The selected emoji * @param emoji The selected emoji
*/ */
fun onEmojiAdded(emoji: Emoji) fun onEmojiAdded(emoji: Emoji)
fun onSoftKeyboardHidden()
fun onSoftKeyboardShown()
fun onEmojiKeyboardHidden()
fun onEmojiKeyboardShown()
} }
} }
\ No newline at end of file
...@@ -41,18 +41,18 @@ ...@@ -41,18 +41,18 @@
<ImageButton <ImageButton
android:id="@+id/button_add_reaction" android:id="@+id/button_add_reaction"
android:layout_width="wrap_content" android:layout_width="24dp"
android:layout_height="wrap_content" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/msg_content_description_show_attachment_options"
android:clickable="false" android:clickable="false"
android:contentDescription="@string/msg_content_description_show_attachment_options"
android:src="@drawable/ic_reaction_24dp" /> android:src="@drawable/ic_reaction_24dp" />
<EditText <chat.rocket.android.widget.emoji.ComposerEditText
android:id="@+id/text_message" android:id="@+id/text_message"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@android:color/transparent" android:background="@android:color/transparent"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment