Debounce click + avatar click

This commit is contained in:
Valere 2019-05-08 15:49:32 +02:00
parent 38abf31889
commit 8fd15f4082
6 changed files with 135 additions and 17 deletions

View File

@ -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)
}
}
}

View File

@ -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?) {

View File

@ -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>()

View File

@ -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

View File

@ -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)
} }

View File

@ -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,17 +36,22 @@ 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)
MatrixLinkify.addLinkMovementMethod(holder.messageView) MatrixLinkify.addLinkMovementMethod(holder.messageView)
val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "", val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "",
TextViewCompat.getTextMetricsParams(holder.messageView), TextViewCompat.getTextMetricsParams(holder.messageView),
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) }
} }