support reactions for TextMessages (in+out) and VoiceMessages (in+out)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-04-07 12:41:35 +02:00
parent f17e892acd
commit 36b786616a
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
11 changed files with 136 additions and 67 deletions

View File

@ -74,6 +74,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
lateinit var message: ChatMessage lateinit var message: ChatMessage
lateinit var voiceMessageInterface: VoiceMessageInterface lateinit var voiceMessageInterface: VoiceMessageInterface
lateinit var reactionsInterface: ReactionsInterface
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) { override fun onBind(message: ChatMessage) {
@ -140,6 +141,12 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
} }
} }
}) })
Reaction().showReactions(message, binding.reactions, context!!)
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
reactionsInterface.onClickReactions(message)
}
} }
private fun updateDownloadState(message: ChatMessage) { private fun updateDownloadState(message: ChatMessage) {
@ -306,6 +313,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
this.voiceMessageInterface = voiceMessageInterface this.voiceMessageInterface = voiceMessageInterface
} }
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
this.reactionsInterface = reactionsInterface
}
companion object { companion object {
private const val TAG = "VoiceInMessageView" private const val TAG = "VoiceInMessageView"
private const val SEEKBAR_START: Int = 0 private const val SEEKBAR_START: Int = 0

View File

@ -36,9 +36,6 @@ import android.text.SpannableString
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -57,7 +54,6 @@ import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.TextMatchers import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
import com.vanniktech.emoji.EmojiTextView
import java.util.HashMap import java.util.HashMap
import javax.inject.Inject import javax.inject.Inject
@ -126,46 +122,12 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable) itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
binding.reactionsEmojiWrapper.removeAllViews() Reaction().showReactions(message, binding.reactions, context!!)
if (message.reactions != null && message.reactions.isNotEmpty()) {
var remainingEmojisToDisplay = MAX_EMOJIS_TO_DISPLAY binding.reactions.reactionsEmojiWrapper.setOnClickListener {
val showInfoAboutMoreEmojis = message.reactions.size > MAX_EMOJIS_TO_DISPLAY
for ((emoji, amount) in message.reactions) {
val reactionEmoji = EmojiTextView(context)
reactionEmoji.text = emoji
val reactionAmount = TextView(context)
reactionAmount.text = amount.toString()
val params = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.setMargins(
DisplayUtils.convertDpToPixel(EMOJI_START_MARGIN, context).toInt(),
0,
DisplayUtils.convertDpToPixel(EMOJI_END_MARGIN, context).toInt(),
0
)
reactionAmount.layoutParams = params
binding.reactionsEmojiWrapper.addView(reactionEmoji)
binding.reactionsEmojiWrapper.addView(reactionAmount)
remainingEmojisToDisplay--
if (remainingEmojisToDisplay == 0 && showInfoAboutMoreEmojis) {
val infoAboutMoreEmojis = TextView(context)
infoAboutMoreEmojis.text = "..."
binding.reactionsEmojiWrapper.addView(infoAboutMoreEmojis)
break
}
}
binding.reactionsEmojiWrapper.setOnClickListener {
reactionsInterface.onClickReactions(message) reactionsInterface.onClickReactions(message)
} }
} }
}
private fun processAuthor(message: ChatMessage) { private fun processAuthor(message: ChatMessage) {
if (!TextUtils.isEmpty(message.actorDisplayName)) { if (!TextUtils.isEmpty(message.actorDisplayName)) {
@ -306,14 +268,11 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
return messageStringInternal return messageStringInternal
} }
fun assignAdapter(reactionsInterface: ReactionsInterface) { fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
this.reactionsInterface = reactionsInterface this.reactionsInterface = reactionsInterface
} }
companion object { companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5 const val TEXT_SIZE_MULTIPLIER = 2.5
const val MAX_EMOJIS_TO_DISPLAY = 4
const val EMOJI_START_MARGIN: Float = 2F
const val EMOJI_END_MARGIN: Float = 8F
} }
} }

View File

@ -61,6 +61,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
@Inject @Inject
var context: Context? = null var context: Context? = null
lateinit var reactionsInterface: ReactionsInterface
override fun onBind(message: ChatMessage) { override fun onBind(message: ChatMessage) {
super.onBind(message) super.onBind(message)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
@ -118,6 +120,12 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
binding.checkMark.setContentDescription(readStatusContentDescriptionString) binding.checkMark.setContentDescription(readStatusContentDescriptionString)
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable) itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
Reaction().showReactions(message, binding.reactions, context!!)
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
reactionsInterface.onClickReactions(message)
}
} }
private fun processParentMessage(message: ChatMessage) { private fun processParentMessage(message: ChatMessage) {
@ -204,6 +212,10 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
return messageString1 return messageString1
} }
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
this.reactionsInterface = reactionsInterface
}
companion object { companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5 const val TEXT_SIZE_MULTIPLIER = 2.5
} }

View File

@ -69,6 +69,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
lateinit var handler: Handler lateinit var handler: Handler
lateinit var voiceMessageInterface: VoiceMessageInterface lateinit var voiceMessageInterface: VoiceMessageInterface
lateinit var reactionsInterface: ReactionsInterface
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) { override fun onBind(message: ChatMessage) {
@ -129,6 +130,12 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
} }
binding.checkMark.setContentDescription(readStatusContentDescriptionString) binding.checkMark.setContentDescription(readStatusContentDescriptionString)
Reaction().showReactions(message, binding.reactions, context!!)
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
reactionsInterface.onClickReactions(message)
}
} }
private fun handleResetVoiceMessageState(message: ChatMessage) { private fun handleResetVoiceMessageState(message: ChatMessage) {
@ -279,6 +286,10 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
this.voiceMessageInterface = voiceMessageInterface this.voiceMessageInterface = voiceMessageInterface
} }
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
this.reactionsInterface = reactionsInterface
}
companion object { companion object {
private const val TAG = "VoiceOutMessageView" private const val TAG = "VoiceOutMessageView"
private const val SEEKBAR_START: Int = 0 private const val SEEKBAR_START: Int = 0

View File

@ -0,0 +1,58 @@
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.utils.DisplayUtils
import com.vanniktech.emoji.EmojiTextView
class Reaction {
fun showReactions(message: ChatMessage, binding: ReactionsInsideMessageBinding, context: Context) {
binding.reactionsEmojiWrapper.removeAllViews()
if (message.reactions != null && message.reactions.isNotEmpty()) {
var remainingEmojisToDisplay = MAX_EMOJIS_TO_DISPLAY
val showInfoAboutMoreEmojis = message.reactions.size > MAX_EMOJIS_TO_DISPLAY
for ((emoji, amount) in message.reactions) {
val reactionEmoji = EmojiTextView(context)
reactionEmoji.text = emoji
val reactionAmount = TextView(context)
reactionAmount.text = amount.toString()
val params = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.setMargins(
DisplayUtils.convertDpToPixel(EMOJI_START_MARGIN, context).toInt(),
0,
DisplayUtils.convertDpToPixel(EMOJI_END_MARGIN, context).toInt(),
0
)
reactionAmount.layoutParams = params
binding.reactionsEmojiWrapper.addView(reactionEmoji)
binding.reactionsEmojiWrapper.addView(reactionAmount)
remainingEmojisToDisplay--
if (remainingEmojisToDisplay == 0 && showInfoAboutMoreEmojis) {
val infoAboutMoreEmojis = TextView(context)
infoAboutMoreEmojis.text = EMOJI_MORE
binding.reactionsEmojiWrapper.addView(infoAboutMoreEmojis)
break
}
}
}
}
companion object {
const val MAX_EMOJIS_TO_DISPLAY = 4
const val EMOJI_START_MARGIN: Float = 2F
const val EMOJI_END_MARGIN: Float = 8F
const val EMOJI_MORE = "..."
}
}

View File

@ -49,14 +49,16 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
public void onBindViewHolder(ViewHolder holder, int position) { public void onBindViewHolder(ViewHolder holder, int position) {
super.onBindViewHolder(holder, position); super.onBindViewHolder(holder, position);
if (holder instanceof IncomingVoiceMessageViewHolder) { if (holder instanceof MagicIncomingTextMessageViewHolder) {
((MagicIncomingTextMessageViewHolder) holder).assignReactionInterface(chatController);
} else if (holder instanceof MagicOutcomingTextMessageViewHolder) {
((MagicOutcomingTextMessageViewHolder) holder).assignReactionInterface(chatController);
} else if (holder instanceof IncomingVoiceMessageViewHolder) {
((IncomingVoiceMessageViewHolder) holder).assignAdapter(chatController); ((IncomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
((IncomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController);
} else if (holder instanceof OutcomingVoiceMessageViewHolder) { } else if (holder instanceof OutcomingVoiceMessageViewHolder) {
((OutcomingVoiceMessageViewHolder) holder).assignAdapter(chatController); ((OutcomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
} ((OutcomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController);
if (holder instanceof MagicIncomingTextMessageViewHolder) {
((MagicIncomingTextMessageViewHolder) holder).assignAdapter(chatController);
} }
} }
} }

View File

@ -86,23 +86,9 @@
android:textIsSelectable="false" android:textIsSelectable="false"
app:layout_alignSelf="center" /> app:layout_alignSelf="center" />
<include
<LinearLayout android:id="@+id/reactions"
android:id="@+id/reactions_emoji_wrapper" layout="@layout/reactions_inside_message" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_wrapBefore="true"
android:orientation="horizontal"
android:layout_marginTop="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="emojis">
</TextView>
</LinearLayout>
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -108,5 +108,9 @@
app:layout_alignSelf="center" app:layout_alignSelf="center"
tools:text="12:38"/> tools:text="12:38"/>
<include
android:id="@+id/reactions"
layout="@layout/reactions_inside_message" />
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -76,5 +76,9 @@
android:contentDescription="@null" android:contentDescription="@null"
app:layout_alignSelf="center" /> app:layout_alignSelf="center" />
<include
android:id="@+id/reactions"
layout="@layout/reactions_inside_message" />
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -104,5 +104,9 @@
app:layout_alignSelf="center" app:layout_alignSelf="center"
android:contentDescription="@null" /> android:contentDescription="@null" />
<include
android:id="@+id/reactions"
layout="@layout/reactions_inside_message" />
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/reactions_emoji_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_alignSelf="flex_start"
app:layout_flexGrow="1"
app:layout_wrapBefore="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="emojis">
</TextView>
</LinearLayout>