From 3bc958c0f17f2ecc2da5db4642fed7d8d8f78fd2 Mon Sep 17 00:00:00 2001 From: thelimitbreaker Date: Sat, 16 Mar 2019 15:17:32 +0530 Subject: [PATCH] Fix Multi-Touch events on recyclerView items Signed-off-by: thelimitbreaker --- .../chatroom/adapter/AttachmentViewHolder.kt | 11 +-- .../chatroom/adapter/BaseViewHolder.kt | 65 +++++++++++------- .../chatroom/adapter/ChatRoomAdapter.kt | 67 ++++++++++++------- .../adapter/MessageReplyViewHolder.kt | 11 +-- .../chatroom/adapter/MessageViewHolder.kt | 11 +-- .../chatroom/adapter/UrlPreviewViewHolder.kt | 9 +-- .../chatrooms/adapter/RoomViewHolder.kt | 44 +++++++++--- .../android/chatrooms/adapter/RoomsAdapter.kt | 48 ++++++++----- .../rocket/android/util/RCRecyclerView.kt | 25 +++++++ .../main/res/layout/fragment_chat_rooms.xml | 6 +- app/src/main/res/layout/message_list.xml | 2 +- 11 files changed, 202 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/chat/rocket/android/util/RCRecyclerView.kt diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/AttachmentViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/AttachmentViewHolder.kt index 2c5ed9fdb8..ca4ac004c0 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/AttachmentViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/AttachmentViewHolder.kt @@ -25,11 +25,12 @@ import kotlinx.android.synthetic.main.item_message_attachment.view.* import timber.log.Timber class AttachmentViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null, - var actionAttachmentOnClickListener: ActionAttachmentOnClickListener -) : BaseViewHolder(itemView, listener, reactionListener) { + itemView: View, + listener: ActionsListener, + multiTouchEventsListener: MultiTouchEventsListener, + reactionListener: EmojiReactionListener? = null, + var actionAttachmentOnClickListener: ActionAttachmentOnClickListener +) : BaseViewHolder(itemView, listener,multiTouchEventsListener ,reactionListener) { private val messageViews = listOf( itemView.text_sender, diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt index be04901f5c..6246047333 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt @@ -18,14 +18,16 @@ import chat.rocket.android.util.extensions.toList import chat.rocket.core.model.Message import chat.rocket.core.model.isSystemMessage import com.google.android.flexbox.FlexDirection -import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.JustifyContent +import kotlinx.coroutines.experimental.delay +import kotlinx.coroutines.experimental.launch abstract class BaseViewHolder>( - itemView: View, - private val listener: ActionsListener, - var reactionListener: EmojiReactionListener? = null + itemView: View, + private val listener: ActionsListener, + private val multiTouchEventsListener: MultiTouchEventsListener, + var reactionListener: EmojiReactionListener? = null ) : RecyclerView.ViewHolder(itemView), MenuItem.OnMenuItemClickListener { var data: T? = null @@ -62,8 +64,9 @@ abstract class BaseViewHolder>( } } - override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List) { - reactionListener?.onReactionLongClicked(shortname, isCustom,url, usernames) + override fun onReactionLongClicked(shortname: String, isCustom: Boolean, + url: String?, usernames: List) { + reactionListener?.onReactionLongClicked(shortname, isCustom, url, usernames) } } @@ -87,33 +90,45 @@ abstract class BaseViewHolder>( fun onActionSelected(item: MenuItem, message: Message) } + interface MultiTouchEventsListener { + fun handleMultiTouchEvents(status: Boolean) + } + private val onClickListener = { view: View -> if (data?.message?.isSystemMessage() == false) { - data?.let { vm -> - vm.message.let { - val menuItems = view.context.inflate(R.menu.message_actions).toList() - menuItems.find { it.itemId == R.id.action_message_unpin }?.apply { - setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin) - isChecked = it.pinned - } + launch { + multiTouchEventsListener.handleMultiTouchEvents(false) + data?.let { vm -> + vm.message.let { + val menuItems = view.context.inflate(R.menu.message_actions).toList() + menuItems.find { it.itemId == R.id.action_message_unpin }?.apply { + setTitle( + if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin) + isChecked = it.pinned + } - menuItems.find { it.itemId == R.id.action_message_star }?.apply { - val isStarred = it.starred?.isNotEmpty() ?: false - setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star) - isChecked = isStarred - } - view.context?.let { - if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) { - with(it.baseContext as AppCompatActivity) { - if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { - val actionsBottomSheet = MessageActionsBottomSheet() - actionsBottomSheet.addItems(menuItems, this@BaseViewHolder) - actionsBottomSheet.show(supportFragmentManager, null) + menuItems.find { it.itemId == R.id.action_message_star }?.apply { + val isStarred = it.starred?.isNotEmpty() ?: false + setTitle( + if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star) + isChecked = isStarred + } + view.context?.let { + if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) { + with(it.baseContext as AppCompatActivity) { + if (this.lifecycle.currentState.isAtLeast( + Lifecycle.State.RESUMED)) { + val actionsBottomSheet = MessageActionsBottomSheet() + actionsBottomSheet.addItems(menuItems, this@BaseViewHolder) + actionsBottomSheet.show(supportFragmentManager, null) + } } } } } } + delay(500) + multiTouchEventsListener.handleMultiTouchEvents(true) } } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt index 20f21e1058..bafe9fee95 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt @@ -13,6 +13,7 @@ import chat.rocket.android.chatroom.uimodel.MessageUiModel import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel import chat.rocket.android.chatroom.uimodel.toViewType import chat.rocket.android.emoji.EmojiReactionListener +import chat.rocket.android.util.RCRecyclerView import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.openTabbedUrl import chat.rocket.core.model.Message @@ -23,15 +24,16 @@ import timber.log.Timber import java.security.InvalidParameterException class ChatRoomAdapter( - private val roomId: String? = null, - private val roomType: String? = null, - private val roomName: String? = null, - private val actionSelectListener: OnActionSelected? = null, - private val enableActions: Boolean = true, - private val reactionListener: EmojiReactionListener? = null, - private val navigator: ChatRoomNavigator? = null + private val roomId: String? = null, + private val roomType: String? = null, + private val roomName: String? = null, + private val actionSelectListener: OnActionSelected? = null, + private val enableActions: Boolean = true, + private val reactionListener: EmojiReactionListener? = null, + private val navigator: ChatRoomNavigator? = null ) : RecyclerView.Adapter>() { private val dataSet = ArrayList>() + private lateinit var ownRecyclerView: RCRecyclerView init { setHasStableIds(true) @@ -42,30 +44,38 @@ class ChatRoomAdapter( BaseUiModel.ViewType.MESSAGE -> { val view = parent.inflate(R.layout.item_message) MessageViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + multiTouchEventListener, + reactionListener ) { userId -> navigator?.toUserDetails(userId) } } BaseUiModel.ViewType.URL_PREVIEW -> { val view = parent.inflate(R.layout.message_url_preview) - UrlPreviewViewHolder(view, actionsListener, reactionListener) + UrlPreviewViewHolder( + view, + actionsListener, + multiTouchEventListener, + reactionListener + ) } BaseUiModel.ViewType.ATTACHMENT -> { val view = parent.inflate(R.layout.item_message_attachment) AttachmentViewHolder( - view, - actionsListener, - reactionListener, - actionAttachmentOnClickListener + view, + actionsListener, + multiTouchEventListener, + reactionListener, + actionAttachmentOnClickListener ) } BaseUiModel.ViewType.MESSAGE_REPLY -> { val view = parent.inflate(R.layout.item_message_reply) MessageReplyViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + multiTouchEventListener, + reactionListener ) { roomName, permalink -> actionSelectListener?.openDirectMessage(roomName, permalink) } @@ -119,6 +129,11 @@ class ChatRoomAdapter( } } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + ownRecyclerView = recyclerView as RCRecyclerView + } + fun clearData() { dataSet.clear() notifyDataSetChanged() @@ -134,7 +149,7 @@ class ChatRoomAdapter( //---At first we will update all already saved elements with received updated ones val filteredDataSet = dataSet.filter { newItem -> val matchedIndex = - this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } + this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } if (matchedIndex > -1) { this.dataSet[matchedIndex] = newItem notifyItemChanged(matchedIndex) @@ -227,6 +242,12 @@ class ChatRoomAdapter( } } + private val multiTouchEventListener = object : BaseViewHolder.MultiTouchEventsListener { + override fun handleMultiTouchEvents(status: Boolean) { + ownRecyclerView.setDispatchTouchStatus(status) + } + } + private val actionsListener = object : BaseViewHolder.ActionsListener { override fun isActionsEnabled(): Boolean = enableActions @@ -284,10 +305,10 @@ class ChatRoomAdapter( fun showMessageInfo(id: String) fun citeMessage( - roomName: String, - roomType: String, - messageId: String, - mentionAuthor: Boolean + roomName: String, + roomType: String, + messageId: String, + mentionAuthor: Boolean ) fun copyMessage(id: String) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageReplyViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageReplyViewHolder.kt index ccda739195..334f113c9a 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageReplyViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageReplyViewHolder.kt @@ -6,11 +6,12 @@ import chat.rocket.android.emoji.EmojiReactionListener import kotlinx.android.synthetic.main.item_message_reply.view.* class MessageReplyViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null, - private val replyCallback: (roomName: String, permalink: String) -> Unit -) : BaseViewHolder(itemView, listener, reactionListener) { + itemView: View, + listener: ActionsListener, + multiTouchEventsListener: MultiTouchEventsListener, + reactionListener: EmojiReactionListener? = null, + private val replyCallback: (roomName: String, permalink: String) -> Unit +) : BaseViewHolder(itemView, listener,multiTouchEventsListener ,reactionListener) { init { setupActionMenu(itemView) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt index 22a83afc43..303b86b744 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt @@ -16,11 +16,12 @@ import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.item_message.view.* class MessageViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null, - private val avatarListener: (String) -> Unit -) : BaseViewHolder(itemView, listener, reactionListener), Drawable.Callback { + itemView: View, + listener: ActionsListener, + multiTouchEventsListener: MultiTouchEventsListener, + reactionListener: EmojiReactionListener? = null, + private val avatarListener: (String) -> Unit +) : BaseViewHolder(itemView, listener,multiTouchEventsListener, reactionListener), Drawable.Callback { init { with(itemView) { diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt index 16aaac7dd1..5d0f2d0965 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt @@ -9,10 +9,11 @@ import chat.rocket.android.util.extensions.openTabbedUrl import kotlinx.android.synthetic.main.message_url_preview.view.* class UrlPreviewViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null -) : BaseViewHolder(itemView, listener, reactionListener) { + itemView: View, + listener: ActionsListener, + multiTouchEventsListener: MultiTouchEventsListener, + reactionListener: EmojiReactionListener? = null +) : BaseViewHolder(itemView, listener,multiTouchEventsListener, reactionListener) { init { setupActionMenu(itemView.url_preview_layout) diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt index 2317178adf..53847939f7 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt @@ -11,18 +11,26 @@ import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.util.extension.setTextViewAppearance import chat.rocket.common.model.RoomType import chat.rocket.common.model.UserStatus -import kotlinx.android.synthetic.main.item_chat.view.* -import kotlinx.android.synthetic.main.unread_messages_badge.view.* +import kotlinx.android.synthetic.main.item_chat.view.image_avatar +import kotlinx.android.synthetic.main.item_chat.view.image_chat_icon +import kotlinx.android.synthetic.main.item_chat.view.text_chat_name +import kotlinx.android.synthetic.main.item_chat.view.text_last_message +import kotlinx.android.synthetic.main.item_chat.view.text_timestamp +import kotlinx.android.synthetic.main.unread_messages_badge.view.text_total_unread_messages +import kotlinx.coroutines.experimental.delay +import kotlinx.coroutines.experimental.launch -class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit) : - ViewHolder(itemView) { +class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit, + private val multiTouchEventsListener: MultiTouchEventsListener) : + ViewHolder(itemView) { private val resources: Resources = itemView.resources private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp, null) private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp, null) private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp, null) private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp, null) private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp, null) - private val offlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp, null) + private val offlineIcon: Drawable = + resources.getDrawable(R.drawable.ic_status_invisible_12dp, null) override fun bindViews(data: RoomItemHolder) { val room = data.data @@ -54,21 +62,37 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit if (room.unread == null) text_total_unread_messages.text = "!" if (room.unread != null) text_total_unread_messages.text = room.unread if (room.mentions) text_total_unread_messages.text = "@${room.unread}" - text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_Unread_TextView) - text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_Unread_TextView) - text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_Unread_TextView) + text_chat_name.setTextViewAppearance(context, + R.style.ChatList_ChatName_Unread_TextView) + text_timestamp.setTextViewAppearance(context, + R.style.ChatList_Timestamp_Unread_TextView) + text_last_message.setTextViewAppearance(context, + R.style.ChatList_LastMessage_Unread_TextView) text_total_unread_messages.isVisible = true } else { text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_TextView) text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_TextView) - text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_TextView) + text_last_message.setTextViewAppearance(context, + R.style.ChatList_LastMessage_TextView) text_total_unread_messages.isInvisible = true } - setOnClickListener { listener(room) } + setOnClickListener { + launch { + multiTouchEventsListener.handleMultiTouchEvents(false) + listener(room) + delay(500) + multiTouchEventsListener.handleMultiTouchEvents(true) + } + } } } + + interface MultiTouchEventsListener { + fun handleMultiTouchEvents(status: Boolean) + } + private fun getRoomDrawable(type: RoomType): Drawable? = when (type) { is RoomType.Channel -> channelIcon is RoomType.PrivateGroup -> groupIcon diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomsAdapter.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomsAdapter.kt index 03d2bce87e..86361649b7 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomsAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomsAdapter.kt @@ -4,36 +4,40 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import chat.rocket.android.R import chat.rocket.android.chatrooms.adapter.model.RoomUiModel +import chat.rocket.android.util.RCRecyclerView import chat.rocket.android.util.extensions.inflate class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) : - RecyclerView.Adapter>() { + RecyclerView.Adapter>() { init { setHasStableIds(true) } + private lateinit var ownRecyclerView: RCRecyclerView var values: List> = ArrayList(0) set(items) { field = items notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> = when (viewType) { - VIEW_TYPE_ROOM -> { - val view = parent.inflate(R.layout.item_chat) - RoomViewHolder(view, listener) - } - VIEW_TYPE_HEADER -> { - val view = parent.inflate(R.layout.item_chatroom_header) - HeaderViewHolder(view) - } - VIEW_TYPE_LOADING -> { - val view = parent.inflate(R.layout.item_loading) - LoadingViewHolder(view) - } - else -> throw IllegalStateException("View type must be either Room, Header or Loading") - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> = + when (viewType) { + VIEW_TYPE_ROOM -> { + val view = parent.inflate(R.layout.item_chat) + RoomViewHolder(view, listener,multiTouchEventsListener ) + } + VIEW_TYPE_HEADER -> { + val view = parent.inflate(R.layout.item_chatroom_header) + HeaderViewHolder(view) + } + VIEW_TYPE_LOADING -> { + val view = parent.inflate(R.layout.item_loading) + LoadingViewHolder(view) + } + else -> throw IllegalStateException( + "View type must be either Room, Header or Loading") + } override fun getItemCount() = values.size @@ -62,6 +66,18 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) : } } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + ownRecyclerView = recyclerView as RCRecyclerView + } + + private val multiTouchEventsListener = object : RoomViewHolder.MultiTouchEventsListener { + override fun handleMultiTouchEvents(status: Boolean) { + ownRecyclerView.setDispatchTouchStatus(status) + } + + } + companion object { const val VIEW_TYPE_ROOM = 1 const val VIEW_TYPE_HEADER = 2 diff --git a/app/src/main/java/chat/rocket/android/util/RCRecyclerView.kt b/app/src/main/java/chat/rocket/android/util/RCRecyclerView.kt new file mode 100644 index 0000000000..06733962f2 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/util/RCRecyclerView.kt @@ -0,0 +1,25 @@ +package chat.rocket.android.util + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.recyclerview.widget.RecyclerView + +class RCRecyclerView : RecyclerView { + + private var dispatchTouchStatus: Boolean = true + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, + defStyle) + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + return if (!dispatchTouchStatus) false + else super.dispatchTouchEvent(ev) + } + + fun setDispatchTouchStatus(status: Boolean) = apply { dispatchTouchStatus = status } +} diff --git a/app/src/main/res/layout/fragment_chat_rooms.xml b/app/src/main/res/layout/fragment_chat_rooms.xml index 1a82b796a5..7eb404b750 100644 --- a/app/src/main/res/layout/fragment_chat_rooms.xml +++ b/app/src/main/res/layout/fragment_chat_rooms.xml @@ -6,11 +6,11 @@ android:layout_height="match_parent" tools:context=".chatrooms.ui.ChatRoomsFragment"> - + android:layout_height="match_parent" + android:layout_below="@+id/text_connection_status" /> -