Commit 5163405f authored by Leonardo Aramaki's avatar Leonardo Aramaki

Implement broadcast reply MessageReplyViewHolder and related classes

parent a1963cc2
...@@ -5,7 +5,19 @@ import android.view.MenuItem ...@@ -5,7 +5,19 @@ import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.viewmodel.* import chat.rocket.android.chatroom.viewmodel.AudioAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.AuthorAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.BaseFileAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.chatroom.viewmodel.ColorAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.GenericFileAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.MessageAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.MessageReplyViewModel
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.UrlPreviewViewModel
import chat.rocket.android.chatroom.viewmodel.VideoAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.toViewType
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.widget.emoji.EmojiReactionListener import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
...@@ -65,6 +77,12 @@ class ChatRoomAdapter( ...@@ -65,6 +77,12 @@ class ChatRoomAdapter(
val view = parent.inflate(R.layout.item_file_attachment) val view = parent.inflate(R.layout.item_file_attachment)
GenericFileAttachmentViewHolder(view, actionsListener, reactionListener) GenericFileAttachmentViewHolder(view, actionsListener, reactionListener)
} }
BaseViewModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply)
MessageReplyViewHolder(view, actionsListener, reactionListener) { permalink ->
}
}
else -> { else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}") throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
} }
...@@ -107,6 +125,7 @@ class ChatRoomAdapter( ...@@ -107,6 +125,7 @@ class ChatRoomAdapter(
is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel) is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel)
is ColorAttachmentViewHolder -> holder.bind(dataSet[position] as ColorAttachmentViewModel) is ColorAttachmentViewHolder -> holder.bind(dataSet[position] as ColorAttachmentViewModel)
is GenericFileAttachmentViewHolder -> holder.bind(dataSet[position] as GenericFileAttachmentViewModel) is GenericFileAttachmentViewHolder -> holder.bind(dataSet[position] as GenericFileAttachmentViewModel)
is MessageReplyViewHolder -> holder.bind(dataSet[position] as MessageReplyViewModel)
} }
} }
......
package chat.rocket.android.chatroom.adapter
import android.view.View
import android.widget.Toast
import chat.rocket.android.chatroom.viewmodel.MessageReplyViewModel
import chat.rocket.android.widget.emoji.EmojiReactionListener
import kotlinx.android.synthetic.main.item_message_reply.view.*
class MessageReplyViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null,
private val replyCallback: (permalink: String) -> Unit
) : BaseViewHolder<MessageReplyViewModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
setupActionMenu(itemView)
}
}
override fun bindViews(data: MessageReplyViewModel) {
with(itemView) {
button_message_reply.setOnClickListener {
replyCallback.invoke(data.rawData.permalink)
}
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.domain
data class MessageReply(
val permalink: String
)
\ No newline at end of file
...@@ -96,16 +96,17 @@ class ChatRoomPresenter @Inject constructor( ...@@ -96,16 +96,17 @@ class ChatRoomPresenter @Inject constructor(
private var chatRoomId: String? = null private var chatRoomId: String? = null
private var chatRoomType: String? = null private var chatRoomType: String? = null
private var chatIsBroadcast: Boolean = false
private val stateChannel = Channel<State>() private val stateChannel = Channel<State>()
private var lastState = manager.state private var lastState = manager.state
fun setupChatRoom(roomId: String) { fun setupChatRoom(roomId: String) {
launchUI(strategy) { launchUI(strategy) {
val canPost = permissions.canPostToReadOnlyChannels() val canPost = permissions.canPostToReadOnlyChannels()
val broadcastChannel = getChatRoomsInteractor.getById(currentServer, roomId)?.run { chatIsBroadcast = getChatRoomsInteractor.getById(currentServer, roomId)?.run {
broadcast broadcast
} ?: false } ?: false
view.onRoomUpdated(canPost, broadcastChannel) view.onRoomUpdated(canPost, chatIsBroadcast)
} }
} }
...@@ -117,7 +118,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -117,7 +118,7 @@ class ChatRoomPresenter @Inject constructor(
try { try {
if (offset == 0L) { if (offset == 0L) {
val localMessages = messagesRepository.getByRoomId(chatRoomId) val localMessages = messagesRepository.getByRoomId(chatRoomId)
val oldMessages = mapper.map(localMessages) val oldMessages = mapper.map(localMessages, chatIsBroadcast)
if (oldMessages.isNotEmpty()) { if (oldMessages.isNotEmpty()) {
view.showMessages(oldMessages) view.showMessages(oldMessages)
loadMissingMessages() loadMissingMessages()
...@@ -156,7 +157,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -156,7 +157,7 @@ class ChatRoomPresenter @Inject constructor(
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
} }
messagesRepository.saveAll(messages) messagesRepository.saveAll(messages)
val allMessages = mapper.map(messages) val allMessages = mapper.map(messages, chatIsBroadcast)
view.showMessages(allMessages) view.showMessages(allMessages)
} }
...@@ -192,7 +193,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -192,7 +193,7 @@ class ChatRoomPresenter @Inject constructor(
try { try {
val message = client.sendMessage(id, chatRoomId, text) val message = client.sendMessage(id, chatRoomId, text)
messagesRepository.save(newMessage) messagesRepository.save(newMessage)
view.showNewMessage(mapper.map(newMessage)) view.showNewMessage(mapper.map(newMessage, chatIsBroadcast))
message message
} catch (ex: Exception) { } catch (ex: Exception) {
// Ok, not very beautiful, but the backend sends us a not valid response // Ok, not very beautiful, but the backend sends us a not valid response
...@@ -333,7 +334,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -333,7 +334,7 @@ class ChatRoomPresenter @Inject constructor(
Timber.d("History: $messages") Timber.d("History: $messages")
if (messages.result.isNotEmpty()) { if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result) val models = mapper.map(messages.result, chatIsBroadcast)
messagesRepository.saveAll(messages.result) messagesRepository.saveAll(messages.result)
launchUI(strategy) { launchUI(strategy) {
...@@ -415,7 +416,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -415,7 +416,7 @@ class ChatRoomPresenter @Inject constructor(
view.showReplyingAction( view.showReplyingAction(
username = getDisplayName(msg.sender), username = getDisplayName(msg.sender),
replyMarkdown = "[ ]($currentServer/$roomType/$room?msg=$id) $mention ", replyMarkdown = "[ ]($currentServer/$roomType/$room?msg=$id) $mention ",
quotedMessage = mapper.map(message).last().preview?.message ?: "" quotedMessage = mapper.map(message, chatIsBroadcast).last().preview?.message ?: ""
) )
} }
} }
...@@ -682,7 +683,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -682,7 +683,7 @@ class ChatRoomPresenter @Inject constructor(
private fun updateMessage(streamedMessage: Message) { private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) { launchUI(strategy) {
val viewModelStreamedMessage = mapper.map(streamedMessage) val viewModelStreamedMessage = mapper.map(streamedMessage, chatIsBroadcast)
val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId)
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id } val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index > -1) { if (index > -1) {
......
...@@ -24,7 +24,8 @@ interface BaseViewModel<out T> { ...@@ -24,7 +24,8 @@ interface BaseViewModel<out T> {
MESSAGE_ATTACHMENT(6), MESSAGE_ATTACHMENT(6),
AUTHOR_ATTACHMENT(7), AUTHOR_ATTACHMENT(7),
COLOR_ATTACHMENT(8), COLOR_ATTACHMENT(8),
GENERIC_FILE_ATTACHMENT(9) GENERIC_FILE_ATTACHMENT(9),
MESSAGE_REPLY(10)
} }
} }
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.core.model.Message
data class MessageReplyViewModel(
override val rawData: MessageReply,
override val messageId: String,
override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>?,
override var preview: Message?,
override var isTemporary: Boolean = false,
override val message: Message
) : BaseViewModel<MessageReply> {
override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE_REPLY.viewType
override val layoutId: Int
get() = R.layout.item_message_reply
}
\ No newline at end of file
...@@ -13,6 +13,7 @@ import androidx.core.text.buildSpannedString ...@@ -13,6 +13,7 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.color import androidx.core.text.color
import androidx.core.text.scale import androidx.core.text.scale
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
...@@ -47,21 +48,21 @@ class ViewModelMapper @Inject constructor( ...@@ -47,21 +48,21 @@ class ViewModelMapper @Inject constructor(
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText) private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
suspend fun map(message: Message): List<BaseViewModel<*>> { suspend fun map(message: Message, onBroadcastChannel: Boolean = false): List<BaseViewModel<*>> {
return translate(message) return translate(message, onBroadcastChannel)
} }
suspend fun map(messages: List<Message>): List<BaseViewModel<*>> = withContext(CommonPool) { suspend fun map(messages: List<Message>, onBroadcastChannel: Boolean = false): List<BaseViewModel<*>> = withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>(messages.size) val list = ArrayList<BaseViewModel<*>>(messages.size)
messages.forEach { messages.forEach {
list.addAll(translate(it)) list.addAll(translate(it, onBroadcastChannel))
} }
return@withContext list return@withContext list
} }
private suspend fun translate(message: Message): List<BaseViewModel<*>> = withContext(CommonPool) { private suspend fun translate(message: Message, onBroadcastChannel: Boolean): List<BaseViewModel<*>> = withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>() val list = ArrayList<BaseViewModel<*>>()
message.urls?.forEach { message.urls?.forEach {
...@@ -86,9 +87,32 @@ class ViewModelMapper @Inject constructor( ...@@ -86,9 +87,32 @@ class ViewModelMapper @Inject constructor(
list[i].nextDownStreamMessage = next list[i].nextDownStreamMessage = next
} }
if (onBroadcastChannel) {
val replyViewModel = mapMessageReply(message)
list.first().nextDownStreamMessage = replyViewModel
list.add(0, replyViewModel)
}
return@withContext list return@withContext list
} }
private fun mapMessageReply(message: Message): MessageReplyViewModel {
val messageReply = MessageReply(permalink = makePermalink(message))
return MessageReplyViewModel(
messageId = message.id,
isTemporary = false,
reactions = emptyList(),
message = message,
preview = mapMessagePreview(message),
rawData = messageReply,
nextDownStreamMessage = null
)
}
private fun makePermalink(message: Message): String {
return "[ ]($currentServer/direct/${message.sender?.username}?msg=${message.id}) "
}
private fun mapUrl(message: Message, url: Url): BaseViewModel<*>? { private fun mapUrl(message: Message, url: Url): BaseViewModel<*>? {
if (url.ignoreParse || url.meta == null) return null if (url.ignoreParse || url.meta == null) return null
......
package chat.rocket.android.util.extensions
import chat.rocket.core.model.Message
import chat.rocket.core.model.isSystemMessage
fun Message.isBroadcastReplyAvailable(isBroadcastChannel: Boolean): Boolean {
return (isTemporary == false) && !isSystemMessage() && isBroadcastChannel
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="4dp" />
<stroke android:color="#1D74F5" android:width="2dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding">
<Button
android:id="@+id/button_message_reply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginTop="5dp"
android:layout_marginStart="56dp"
android:background="@drawable/message_reply_button_bg"
android:text="@string/action_msg_reply"
android:textAllCaps="false"
android:textColor="#1D74F5"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="parent" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/button_message_reply"
app:layout_constraintStart_toStartOf="@+id/button_message_reply"
app:layout_constraintTop_toBottomOf="@+id/button_message_reply" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment