From 8fd15f40820763cabb1aa0187e992c8b27be1132 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 May 2019 15:49:32 +0200 Subject: [PATCH] Debounce click + avatar click --- .../core/utils/DebouncedClickListener.kt | 23 ++++++ .../home/room/detail/RoomDetailFragment.kt | 21 +++-- .../timeline/TimelineEventController.kt | 2 + .../timeline/factory/MessageItemFactory.kt | 82 +++++++++++++++++-- .../detail/timeline/item/AbsMessageItem.kt | 9 ++ .../detail/timeline/item/MessageTextItem.kt | 15 ++-- 6 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/core/utils/DebouncedClickListener.kt diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/DebouncedClickListener.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/DebouncedClickListener.kt new file mode 100644 index 0000000000..dbfbe4fe79 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/utils/DebouncedClickListener.kt @@ -0,0 +1,23 @@ +package im.vector.riotredesign.core.utils + +import android.view.View + + +/** + * Simple Debounced OnClickListener + * Safe to use in different views + */ +class DebouncedClickListener(val original: View.OnClickListener, private val minimumInterval: Long = 400) : View.OnClickListener { + private val lastClickMap = HashMap() + + override fun onClick(clickedView: View) { + val previousClickTimestamp = lastClickMap[clickedView] + val currentTimestamp = System.currentTimeMillis() + + lastClickMap[clickedView] = currentTimestamp + + if (previousClickTimestamp == null || currentTimestamp - previousClickTimestamp.toLong() > minimumInterval) { + original.onClick(clickedView) + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 9162ced3b7..3e8a11a8fb 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -49,11 +49,7 @@ import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent -import im.vector.matrix.android.api.session.room.model.message.MessageFileContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent -import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.user.model.User import im.vector.reactions.EmojiReactionPickerActivity @@ -429,6 +425,17 @@ class RoomDetailFragment : vectorBaseActivity.notImplemented() } + override fun onEventCellClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View) { + val roomId = (arguments?.get(MvRx.KEY_ARG) as? RoomDetailArgs)?.roomId + if (roomId.isNullOrBlank()) { + Timber.e("Missing RoomId, cannot open bottomsheet") + return + } + MessageActionsBottomSheet + .newInstance(eventId, roomId, informationData) + .show(requireActivity().supportFragmentManager, "MESSAGE_CONTEXTUAL_ACTIONS") + } + // AutocompleteUserPresenter.Callback override fun onEventLongClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -443,7 +450,9 @@ class RoomDetailFragment : return true } - + override fun onAvatarClicked(informationData: MessageInformationData) { + vectorBaseActivity.notImplemented() + } // AutocompleteUserPresenter.Callback override fun onQueryUsers(query: CharSequence?) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 8533eb8c05..32b2f87311 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -54,7 +54,9 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) fun onFileMessageClicked(messageFileContent: MessageFileContent) fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) + fun onEventCellClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View) fun onEventLongClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean + fun onAvatarClicked(informationData: MessageInformationData) } private val collapsedEventIds = linkedSetOf() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 36e5687647..2dc3db94e6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -16,11 +16,9 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory -import android.text.Spannable -import android.text.SpannableString import android.text.SpannableStringBuilder +import android.view.View import androidx.annotation.ColorRes -import androidx.core.text.toSpannable import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.session.events.model.EventType @@ -33,6 +31,7 @@ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.linkify.VectorLinkify import im.vector.riotredesign.core.resources.ColorProvider +import im.vector.riotredesign.core.utils.DebouncedClickListener import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider @@ -102,7 +101,18 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .informationData(informationData) .filename(messageContent.body) .iconRes(R.drawable.filetype_audio) - .clickListener { _ -> callback?.onAudioMessageClicked(messageContent) } + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) + .clickListener( + DebouncedClickListener(View.OnClickListener { _ -> + callback?.onAudioMessageClicked(messageContent) + })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false @@ -116,7 +126,18 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .informationData(informationData) .filename(messageContent.body) .iconRes(R.drawable.filetype_attachment) - .clickListener { _ -> callback?.onFileMessageClicked(messageContent) } + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) + .clickListener( + DebouncedClickListener(View.OnClickListener { _ -> + callback?.onFileMessageClicked(messageContent) + })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false @@ -147,7 +168,19 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .playable(messageContent.info?.mimeType == "image/gif") .informationData(informationData) .mediaData(data) - .clickListener { view -> callback?.onImageMessageClicked(messageContent, data, view) } + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .clickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onImageMessageClicked(messageContent, data, view) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) + .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false @@ -178,6 +211,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .playable(true) .informationData(informationData) .mediaData(thumbnailData) + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) } .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) @@ -198,6 +239,19 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageTextItem_() .message(linkifiedBody) .informationData(informationData) + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + //click on the text + .clickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false @@ -219,6 +273,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageTextItem_() .message(message) .informationData(informationData) + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false @@ -236,6 +298,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageTextItem_() .message(message) .informationData(informationData) + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(eventId, informationData, messageContent, view) + })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view) ?: false diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index cbcd658988..4f2b02db5b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -20,6 +20,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute +import com.jakewharton.rxbinding2.view.RxView import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.features.home.AvatarRenderer @@ -31,10 +32,17 @@ abstract class AbsMessageItem : VectorEpoxyModel() @EpoxyAttribute var longClickListener: View.OnLongClickListener? = null + @EpoxyAttribute + var cellClickListener: View.OnClickListener? = null + + @EpoxyAttribute + var avatarClickListener: View.OnClickListener? = null + override fun bind(holder: H) { super.bind(holder) if (informationData.showInformation) { holder.avatarImageView.visibility = View.VISIBLE + holder.avatarImageView.setOnClickListener(avatarClickListener) holder.memberNameView.visibility = View.VISIBLE holder.timeView.visibility = View.VISIBLE holder.timeView.text = informationData.time @@ -45,6 +53,7 @@ abstract class AbsMessageItem : VectorEpoxyModel() holder.memberNameView.visibility = View.GONE holder.timeView.visibility = View.GONE } + holder.view.setOnClickListener(cellClickListener) holder.view.setOnLongClickListener(longClickListener) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt index aede2e0c33..75c24225b3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -16,7 +16,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item -import android.text.Spannable +import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.AppCompatTextView @@ -36,17 +36,22 @@ import kotlinx.coroutines.withContext @EpoxyModelClass(layout = R.layout.item_timeline_event_text_message) abstract class MessageTextItem : AbsMessageItem() { - @EpoxyAttribute var message: CharSequence? = null - @EpoxyAttribute override lateinit var informationData: MessageInformationData + @EpoxyAttribute + var message: CharSequence? = null + @EpoxyAttribute + override lateinit var informationData: MessageInformationData + @EpoxyAttribute + var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { super.bind(holder) MatrixLinkify.addLinkMovementMethod(holder.messageView) val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "", - TextViewCompat.getTextMetricsParams(holder.messageView), - null) + TextViewCompat.getTextMetricsParams(holder.messageView), + null) holder.messageView.setTextFuture(textFuture) holder.messageView.renderSendState() + holder.messageView.setOnClickListener (clickListener) holder.messageView.setOnLongClickListener(longClickListener) findPillsAndProcess { it.bind(holder.messageView) } }