Debounce click + avatar click
This commit is contained in:
parent
38abf31889
commit
8fd15f4082
|
@ -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<View, Long>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,11 +49,7 @@ import com.otaliastudios.autocomplete.Autocomplete
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
import com.otaliastudios.autocomplete.CharPolicy
|
import com.otaliastudios.autocomplete.CharPolicy
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.*
|
||||||
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.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.reactions.EmojiReactionPickerActivity
|
import im.vector.reactions.EmojiReactionPickerActivity
|
||||||
|
@ -429,6 +425,17 @@ class RoomDetailFragment :
|
||||||
vectorBaseActivity.notImplemented()
|
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
|
// AutocompleteUserPresenter.Callback
|
||||||
override fun onEventLongClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean {
|
override fun onEventLongClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean {
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
@ -443,7 +450,9 @@ class RoomDetailFragment :
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onAvatarClicked(informationData: MessageInformationData) {
|
||||||
|
vectorBaseActivity.notImplemented()
|
||||||
|
}
|
||||||
// AutocompleteUserPresenter.Callback
|
// AutocompleteUserPresenter.Callback
|
||||||
|
|
||||||
override fun onQueryUsers(query: CharSequence?) {
|
override fun onQueryUsers(query: CharSequence?) {
|
||||||
|
|
|
@ -54,7 +54,9 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
|
||||||
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
||||||
fun onFileMessageClicked(messageFileContent: MessageFileContent)
|
fun onFileMessageClicked(messageFileContent: MessageFileContent)
|
||||||
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
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 onEventLongClicked(eventId: String, informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean
|
||||||
|
fun onAvatarClicked(informationData: MessageInformationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val collapsedEventIds = linkedSetOf<String>()
|
private val collapsedEventIds = linkedSetOf<String>()
|
||||||
|
|
|
@ -16,11 +16,9 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.SpannableString
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.view.View
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.core.text.toSpannable
|
|
||||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||||
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.extensions.localDateTime
|
||||||
import im.vector.riotredesign.core.linkify.VectorLinkify
|
import im.vector.riotredesign.core.linkify.VectorLinkify
|
||||||
import im.vector.riotredesign.core.resources.ColorProvider
|
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.TimelineEventController
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
|
@ -102,7 +101,18 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.iconRes(R.drawable.filetype_audio)
|
.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 ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
@ -116,7 +126,18 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.iconRes(R.drawable.filetype_attachment)
|
.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 ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
@ -147,7 +168,19 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
.playable(messageContent.info?.mimeType == "image/gif")
|
.playable(messageContent.info?.mimeType == "image/gif")
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.mediaData(data)
|
.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 ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
@ -178,6 +211,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
.playable(true)
|
.playable(true)
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.mediaData(thumbnailData)
|
.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) }
|
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
|
@ -198,6 +239,19 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(linkifiedBody)
|
.message(linkifiedBody)
|
||||||
.informationData(informationData)
|
.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 ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
@ -219,6 +273,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(message)
|
.message(message)
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
|
.avatarClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
|
callback?.onAvatarClicked(informationData)
|
||||||
|
}))
|
||||||
|
.cellClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
|
callback?.onEventCellClicked(eventId, informationData, messageContent, view)
|
||||||
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
@ -236,6 +298,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(message)
|
.message(message)
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
|
.avatarClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
|
callback?.onAvatarClicked(informationData)
|
||||||
|
}))
|
||||||
|
.cellClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
|
callback?.onEventCellClicked(eventId, informationData, messageContent, view)
|
||||||
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(eventId, informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.jakewharton.rxbinding2.view.RxView
|
||||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
|
@ -31,10 +32,17 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : VectorEpoxyModel<H>()
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var longClickListener: View.OnLongClickListener? = null
|
var longClickListener: View.OnLongClickListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var cellClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var avatarClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: H) {
|
override fun bind(holder: H) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
if (informationData.showInformation) {
|
if (informationData.showInformation) {
|
||||||
holder.avatarImageView.visibility = View.VISIBLE
|
holder.avatarImageView.visibility = View.VISIBLE
|
||||||
|
holder.avatarImageView.setOnClickListener(avatarClickListener)
|
||||||
holder.memberNameView.visibility = View.VISIBLE
|
holder.memberNameView.visibility = View.VISIBLE
|
||||||
holder.timeView.visibility = View.VISIBLE
|
holder.timeView.visibility = View.VISIBLE
|
||||||
holder.timeView.text = informationData.time
|
holder.timeView.text = informationData.time
|
||||||
|
@ -45,6 +53,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : VectorEpoxyModel<H>()
|
||||||
holder.memberNameView.visibility = View.GONE
|
holder.memberNameView.visibility = View.GONE
|
||||||
holder.timeView.visibility = View.GONE
|
holder.timeView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
holder.view.setOnClickListener(cellClickListener)
|
||||||
holder.view.setOnLongClickListener(longClickListener)
|
holder.view.setOnLongClickListener(longClickListener)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail.timeline.item
|
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.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
@ -36,8 +36,12 @@ import kotlinx.coroutines.withContext
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_text_message)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_text_message)
|
||||||
abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute var message: CharSequence? = null
|
@EpoxyAttribute
|
||||||
@EpoxyAttribute override lateinit var informationData: MessageInformationData
|
var message: CharSequence? = null
|
||||||
|
@EpoxyAttribute
|
||||||
|
override lateinit var informationData: MessageInformationData
|
||||||
|
@EpoxyAttribute
|
||||||
|
var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
@ -47,6 +51,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
null)
|
null)
|
||||||
holder.messageView.setTextFuture(textFuture)
|
holder.messageView.setTextFuture(textFuture)
|
||||||
holder.messageView.renderSendState()
|
holder.messageView.renderSendState()
|
||||||
|
holder.messageView.setOnClickListener (clickListener)
|
||||||
holder.messageView.setOnLongClickListener(longClickListener)
|
holder.messageView.setOnLongClickListener(longClickListener)
|
||||||
findPillsAndProcess { it.bind(holder.messageView) }
|
findPillsAndProcess { it.bind(holder.messageView) }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue