AttachmentViewHolder.kt
package chat.rocket.android.chatroom.adapter
import android.animation.ValueAnimator
import android.content.Intent
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.AttachmentUiModel
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.helper.ImageHelper
import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.isVisible
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.android.util.extensions.setOnClickListener
import chat.rocket.core.model.attachment.actions.Action
import com.facebook.drawee.backends.pipeline.Fresco
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<AttachmentUiModel>(itemView, listener, reactionListener) {
private val messageViews = listOf<View>(
itemView.text_sender,
itemView.text_message_time,
itemView.text_content,
itemView.text_view_more
)
private val audioVideoViews = listOf<View>(
itemView.audio_video_attachment,
itemView.play_button
)
private val quoteBarColor = ContextCompat.getColor(itemView.context, R.color.quoteBar)
init {
with(itemView) {
setupActionMenu(attachment_container)
}
}
override fun bindViews(data: AttachmentUiModel) {
with(itemView) {
file_name.isVisible = false
text_file_name.isVisible = false
// Media attachments
image_attachment.isVisible = data.hasImage
audio_video_attachment.isVisible = data.hasAudioOrVideo
when {
data.hasImage -> bindImage(data)
data.hasAudioOrVideo -> bindAudioOrVideo(data)
data.hasFile -> bindFile(data)
}
// File description - self describing
file_description.isVisible = data.hasDescription
file_description.text = data.description
// Message attachment
messageViews.isVisible = data.hasMessage
if (data.hasMessage) {
bindMessage(data)
}
// Author
author_icon.isInvisible = !(data.hasAuthorIcon && data.hasAuthorLink && data.hasAuthorName)
text_author_name.isVisible = data.hasAuthorLink && data.hasAuthorName
if (data.hasAuthorLink && data.hasAuthorName) {
bindAuthorLink(data)
}
// If not media or message, show the text with quote bar
attachment_text.isVisible = !data.hasMedia && !data.hasMessage && data.hasText
attachment_text.text = data.text
// If it has titleLink and is not "type = file" show the title/titleLink on this field.
file_name_not_file_type.isVisible = !data.hasFile && data.hasTitleLink
if (!data.hasFile && data.hasTitleLink) {
bindTitleLink(data)
}
// Fields
text_fields.isVisible = data.hasFields
if (data.hasFields) {
bindFields(data)
}
// Actions
actions_list.isVisible = data.hasActions
if (data.hasActions) {
bindActions(data)
}
// Quote bar
quote_bar.isVisible = shouldShowQuoteBar(data)
if (data.color != null) {
quote_bar.setColorFilter(data.color)
} else {
quote_bar.setColorFilter(quoteBarColor)
}
}
}
private fun shouldShowQuoteBar(data: AttachmentUiModel): Boolean {
return data.hasFields || data.hasActions || (data.hasAuthorLink && data.hasAuthorName)
|| data.hasMessage || (!data.hasFile && data.hasTitleLink)
|| (!data.hasMedia && !data.hasMessage && data.hasText)
}
private fun bindImage(data: AttachmentUiModel) {
with(itemView) {
val controller = Fresco.newDraweeControllerBuilder().apply {
setUri(data.imageUrl)
autoPlayAnimations = true
oldController = image_attachment.controller
}.build()
image_attachment.controller = controller
image_attachment.setOnClickListener {
ImageHelper.openImage(
context,
data.imageUrl!!,
data.title?.toString()
)
}
file_name.isVisible = data.hasTitle
file_name.text = data.title
file_text.isVisible = data.hasText
file_text.text = data.text
}
}
private fun bindAudioOrVideo(data: AttachmentUiModel) {
with(itemView) {
file_name.isVisible = data.hasTitle
file_name.text = data.title
file_text.isVisible = data.hasText
file_text.text = data.text
val url = if (data.hasVideo) data.videoUrl else data.audioUrl
audioVideoViews.setOnClickListener { view ->
url?.let {
PlayerActivity.play(view.context, url)
}
}
}
}
private fun bindFile(data: AttachmentUiModel) {
with(itemView) {
text_file_name.isVisible = true
text_file_name.content = data.title
text_file_name.setOnClickListener {
it.context.startActivity(Intent(Intent.ACTION_VIEW, data.titleLink?.toUri()))
}
}
}
private fun bindMessage(data: AttachmentUiModel) {
with(itemView) {
val collapsedHeight = context.resources.getDimensionPixelSize(R.dimen.quote_collapsed_height)
val viewMore = context.getString(R.string.msg_view_more)
val viewLess = context.getString(R.string.msg_view_less)
text_message_time.text = data.timestamp
text_sender.text = data.authorName
text_content.text = data.text
text_view_more.isVisible = true
text_view_more.text = if (isExpanded()) viewLess else viewMore
val lp = text_content.layoutParams
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
text_content.layoutParams = lp
text_content.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int,
oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
val textMeasuredHeight = bottom - top
if (collapsedHeight >= textMeasuredHeight) {
text_view_more.isVisible = false
text_content.removeOnLayoutChangeListener(this)
return
}
val expandAnimation = ValueAnimator
.ofInt(collapsedHeight, textMeasuredHeight)
.setDuration(300)
expandAnimation.interpolator = LinearInterpolator()
val collapseAnimation = ValueAnimator
.ofInt(textMeasuredHeight, collapsedHeight)
.setDuration(300)
collapseAnimation.interpolator = LinearInterpolator()
expandAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
if (value == textMeasuredHeight) {
text_view_more.text = viewLess
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
} else {
text_view_more.text = viewMore
}
}
collapseAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
if (value == textMeasuredHeight) {
text_view_more.text = viewLess
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
} else {
text_view_more.text = viewMore
}
}
text_view_more.setOnClickListener {
if (expandAnimation.isRunning) return@setOnClickListener
if (isExpanded()) {
collapseAnimation.start()
} else {
expandAnimation.start()
}
}
text_content.removeOnLayoutChangeListener(this)
}
})
}
}
private fun bindAuthorLink(data: AttachmentUiModel) {
with(itemView) {
author_icon.setImageURI(data.authorIcon)
text_author_name.content = data.authorName
text_author_name.setOnClickListener {
openTabbedUrl(data.authorLink)
}
}
}
private fun bindTitleLink(data: AttachmentUiModel) {
with(itemView) {
val filename = data.title ?: data.titleLink
file_name_not_file_type.text = filename
file_name_not_file_type.setOnClickListener {
openTabbedUrl(data.titleLink)
}
}
}
private fun bindFields(data: AttachmentUiModel) {
with(itemView) {
text_fields.content = data.fields
}
}
private fun bindActions(data: AttachmentUiModel) {
val actions = data.actions
val alignment = data.buttonAlignment
Timber.d("no of actions : ${actions!!.size} : $actions")
with(itemView) {
attachment_text.isVisible = data.hasText
attachment_text.text = data.text
actions_list.layoutManager = LinearLayoutManager(itemView.context,
when (alignment) {
"horizontal" -> LinearLayoutManager.HORIZONTAL
else -> LinearLayoutManager.VERTICAL //Default
}, false)
actions_list.adapter = ActionsListAdapter(actions, actionAttachmentOnClickListener)
}
}
private fun isExpanded(): Boolean {
with(itemView) {
val lp = text_content.layoutParams
return lp.height == ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}
interface ActionAttachmentOnClickListener {
fun onActionClicked(view: View, action: Action)
}