ChatRoomAdapter.kt 11.9 KB
Newer Older
1 2
package chat.rocket.android.chatroom.adapter

3
import android.view.MenuItem
4
import android.view.View
5
import android.view.ViewGroup
6
import androidx.recyclerview.widget.RecyclerView
7
import chat.rocket.android.R
8
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
Filipe de Lima Brito's avatar
Filipe de Lima Brito committed
9
import chat.rocket.android.chatroom.uimodel.*
10
import chat.rocket.android.emoji.EmojiReactionListener
11
import chat.rocket.android.util.extensions.inflate
12
import chat.rocket.android.util.extensions.openTabbedUrl
13
import chat.rocket.core.model.Message
14 15
import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ButtonAction
16
import chat.rocket.core.model.isSystemMessage
17 18 19 20
import timber.log.Timber
import java.security.InvalidParameterException

class ChatRoomAdapter(
21
    private val roomId: String? = null,
22 23
    private val roomType: String? = null,
    private val roomName: String? = null,
24
    private val actionSelectListener: OnActionSelected? = null,
25
    private val enableActions: Boolean = true,
26 27
    private val reactionListener: EmojiReactionListener? = null,
    private val navigator: ChatRoomNavigator? = null
28
) : RecyclerView.Adapter<BaseViewHolder<*>>() {
29
    private val dataSet = ArrayList<BaseUiModel<*>>()
30 31 32 33 34 35

    init {
        setHasStableIds(true)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
36
        return when (viewType.toViewType()) {
37
            BaseUiModel.ViewType.MESSAGE -> {
38
                val view = parent.inflate(R.layout.item_message)
39 40 41
                MessageViewHolder(
                    view,
                    actionsListener,
Filipe de Lima Brito's avatar
Filipe de Lima Brito committed
42 43 44 45
                    reactionListener,
                    { userId -> navigator?.toUserDetails(userId) },
                    { roomId?.let { navigator?.toVideoConferencing(it) } }
                )
46
            }
47
            BaseUiModel.ViewType.URL_PREVIEW -> {
48
                val view = parent.inflate(R.layout.message_url_preview)
49
                UrlPreviewViewHolder(view, actionsListener, reactionListener)
50
            }
Lucio Maciel's avatar
Lucio Maciel committed
51 52
            BaseUiModel.ViewType.ATTACHMENT -> {
                val view = parent.inflate(R.layout.item_message_attachment)
53 54 55 56 57 58
                AttachmentViewHolder(
                    view,
                    actionsListener,
                    reactionListener,
                    actionAttachmentOnClickListener
                )
59
            }
60
            BaseUiModel.ViewType.MESSAGE_REPLY -> {
61
                val view = parent.inflate(R.layout.item_message_reply)
62 63 64 65 66
                MessageReplyViewHolder(
                    view,
                    actionsListener,
                    reactionListener
                ) { roomName, permalink ->
67
                    actionSelectListener?.openDirectMessage(roomName, permalink)
68 69
                }
            }
70 71 72 73 74 75
            else -> {
                throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
            }
        }
    }

karthyks's avatar
karthyks committed
76
    override fun getItemViewType(position: Int): Int = dataSet[position].viewType
77

karthyks's avatar
karthyks committed
78
    override fun getItemCount(): Int = dataSet.size
79 80

    override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
81 82 83 84
        if (holder !is MessageViewHolder) {
            if (position + 1 < itemCount) {
                val messageAbove = dataSet[position + 1]
                if (messageAbove.messageId == dataSet[position].messageId) {
85
                    messageAbove.nextDownStreamMessage = dataSet[position]
86 87 88 89
                }
            }
        } else {
            if (position == 0) {
90 91
                dataSet[0].nextDownStreamMessage = null
            } else if (position - 1 > 0) {
92
                if (dataSet[position - 1].messageId != dataSet[position].messageId) {
93
                    dataSet[position].nextDownStreamMessage = null
94 95 96 97
                }
            }
        }

98
        when (holder) {
99
            is MessageViewHolder ->
100
                holder.bind(dataSet[position] as MessageUiModel)
101
            is UrlPreviewViewHolder ->
102
                holder.bind(dataSet[position] as UrlPreviewUiModel)
103
            is MessageReplyViewHolder ->
104
                holder.bind(dataSet[position] as MessageReplyUiModel)
Lucio Maciel's avatar
Lucio Maciel committed
105 106
            is AttachmentViewHolder ->
                holder.bind(dataSet[position] as AttachmentUiModel)
107 108 109 110 111 112
        }
    }

    override fun getItemId(position: Int): Long {
        val model = dataSet[position]
        return when (model) {
113
            is MessageUiModel -> model.messageId.hashCode().toLong()
Lucio Maciel's avatar
Lucio Maciel committed
114
            is AttachmentUiModel -> model.id
115 116 117 118
            else -> return position.toLong()
        }
    }

119 120 121 122 123
    fun clearData() {
        dataSet.clear()
        notifyDataSetChanged()
    }

124
    fun appendData(dataSet: List<BaseUiModel<*>>) {
125 126 127 128 129
        val previousDataSetSize = this.dataSet.size
        this.dataSet.addAll(dataSet)
        notifyItemChanged(previousDataSetSize, dataSet.size)
    }

130
    fun prependData(dataSet: List<BaseUiModel<*>>) {
131
        //---At first we will update all already saved elements with received updated ones
132
        val filteredDataSet = dataSet.filter { newItem ->
133 134
            val matchedIndex =
                this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
135 136 137
            if (matchedIndex > -1) {
                this.dataSet[matchedIndex] = newItem
                notifyItemChanged(matchedIndex)
138
            }
139
            return@filter (matchedIndex < 0)
Leonardo Aramaki's avatar
Leonardo Aramaki committed
140
        }
Kiryl Vashyla's avatar
Kiryl Vashyla committed
141
        val minAdditionDate = filteredDataSet.minBy { it.message.timestamp } ?: return
142
        //---In the most cases we will just add new elements to the top of messages heap
Lucio Maciel's avatar
Lucio Maciel committed
143
        if (this.dataSet.isEmpty() || minAdditionDate.message.timestamp > this.dataSet[0].message.timestamp) {
Kiryl Vashyla's avatar
Kiryl Vashyla committed
144 145
            this.dataSet.addAll(0, filteredDataSet)
            notifyItemRangeInserted(0, filteredDataSet.size)
146 147 148 149
            return
        }
        //---Else branch: merging messages---
        //---We are inserting new received elements into set. Sort them by time+type and show
Kiryl Vashyla's avatar
Kiryl Vashyla committed
150
        if (filteredDataSet.isEmpty()) return
151
        this.dataSet.addAll(0, filteredDataSet)
152 153 154 155 156 157 158
        val tmp = this.dataSet.sortedWith(Comparator { t, t2 ->
            val timeComparison = t.message.timestamp.compareTo(t2.message.timestamp)
            if (timeComparison == 0) {
                return@Comparator t.viewType.compareTo(t2.viewType)
            }
            timeComparison
        }).reversed()
159 160 161
        this.dataSet.clear()
        this.dataSet.addAll(tmp)
        notifyDataSetChanged()
162 163
    }

Lucio Maciel's avatar
Lucio Maciel committed
164
    fun updateItem(message: BaseUiModel<*>): Boolean {
165
        val index = dataSet.indexOfLast { it.messageId == message.messageId }
166
        val indexOfNext = dataSet.indexOfFirst { it.messageId == message.messageId }
167 168 169
        Timber.d("index: $index")
        if (index > -1) {
            dataSet[index] = message
karthyks's avatar
karthyks committed
170
            dataSet.forEachIndexed { ind, viewModel ->
171 172 173 174
                if (viewModel.messageId == message.messageId) {
                    if (viewModel.nextDownStreamMessage == null) {
                        viewModel.reactions = message.reactions
                    }
karthyks's avatar
karthyks committed
175
                    notifyItemChanged(ind)
176
                }
177
            }
178
            // Delete message only if current is a system message update, i.e.: Message Removed
179 180 181
            if (message.message.isSystemMessage() && indexOfNext > -1 && indexOfNext != index) {
                dataSet.removeAt(indexOfNext)
                notifyItemRemoved(indexOfNext)
182
            }
Lucio Maciel's avatar
Lucio Maciel committed
183
            return true
184
        }
Lucio Maciel's avatar
Lucio Maciel committed
185
        return false
186 187 188 189 190 191 192 193 194 195 196 197 198
    }

    fun removeItem(messageId: String) {
        val index = dataSet.indexOfFirst { it.messageId == messageId }
        if (index > -1) {
            val oldSize = dataSet.size
            val newSet = dataSet.filterNot { it.messageId == messageId }
            dataSet.clear()
            dataSet.addAll(newSet)
            val newSize = dataSet.size
            notifyItemRangeRemoved(index, oldSize - newSize)
        }
    }
199

200
    private val actionAttachmentOnClickListener = object : ActionAttachmentOnClickListener {
201
        override fun onActionClicked(view: View, action: Action) {
202 203
            val temp = action as ButtonAction
            if (temp.url != null && temp.isWebView != null) {
204
                if (temp.isWebView == true) {
205 206
                    //TODO: Open in a configurable sizable webview
                    Timber.d("Open in a configurable sizable webview")
207 208
                } else {
                    //Open in chrome custom tab
209
                    temp.url?.let { view.openTabbedUrl(it) }
210 211
                }
            } else if (temp.message != null && temp.isMessageInChatWindow != null) {
212
                if (temp.isMessageInChatWindow == true) {
213
                    //Send to chat window
214
                    temp.message?.let {
215
                        if (roomId != null) {
216
                            actionSelectListener?.sendMessage(roomId, it)
217 218 219
                        }
                    }
                } else {
220 221
                    //TODO: Send to bot but not in chat window
                    Timber.d("Send to bot but not in chat window")
222 223 224 225 226
                }
            }
        }
    }

Leonardo Aramaki's avatar
Leonardo Aramaki committed
227
    private val actionsListener = object : BaseViewHolder.ActionsListener {
228

229 230 231 232 233
        override fun isActionsEnabled(): Boolean = enableActions

        override fun onActionSelected(item: MenuItem, message: Message) {
            message.apply {
                when (item.itemId) {
234
                    R.id.action_message_info -> {
235
                        actionSelectListener?.showMessageInfo(id)
236
                    }
237
                    R.id.action_message_reply -> {
238
                        if (roomName != null && roomType != null) {
239
                            actionSelectListener?.citeMessage(roomName, roomType, id, true)
240
                        }
241 242
                    }
                    R.id.action_message_quote -> {
243
                        if (roomName != null && roomType != null) {
244
                            actionSelectListener?.citeMessage(roomName, roomType, id, false)
245
                        }
246 247
                    }
                    R.id.action_message_copy -> {
248
                        actionSelectListener?.copyMessage(id)
249 250
                    }
                    R.id.action_message_edit -> {
251
                        actionSelectListener?.editMessage(roomId, id, message.message)
252 253
                    }
                    R.id.action_message_star -> {
karthyks's avatar
karthyks committed
254
                        actionSelectListener?.toggleStar(id, !item.isChecked)
255 256
                    }
                    R.id.action_message_unpin -> {
karthyks's avatar
karthyks committed
257
                        actionSelectListener?.togglePin(id, !item.isChecked)
258
                    }
divyanshu's avatar
divyanshu committed
259
                    R.id.action_message_delete -> {
260 261 262 263 264
                        actionSelectListener?.deleteMessage(roomId, id)
                    }
                    R.id.action_menu_msg_react -> {
                        actionSelectListener?.showReactions(id)
                    }
265 266 267
                    R.id.action_message_permalink -> {
                        actionSelectListener?.copyPermalink(id)
                    }
268 269 270
                    R.id.action_message_report -> {
                        actionSelectListener?.reportMessage(id)
                    }
271 272
                    else -> {
                        TODO("Not implemented")
divyanshu's avatar
divyanshu committed
273
                    }
274 275 276 277
                }
            }
        }
    }
278 279

    interface OnActionSelected {
280

281
        fun showMessageInfo(id: String)
282

283 284 285 286 287 288
        fun citeMessage(
            roomName: String,
            roomType: String,
            messageId: String,
            mentionAuthor: Boolean
        )
289

290
        fun copyMessage(id: String)
291

292
        fun editMessage(roomId: String, messageId: String, text: String)
293

karthyks's avatar
karthyks committed
294
        fun toggleStar(id: String, star: Boolean)
295

karthyks's avatar
karthyks committed
296
        fun togglePin(id: String, pin: Boolean)
297

298
        fun deleteMessage(roomId: String, id: String)
299

300
        fun showReactions(id: String)
301

302
        fun openDirectMessage(roomName: String, message: String)
303

304
        fun sendMessage(chatRoomId: String, text: String)
305

306
        fun copyPermalink(id: String)
307 308

        fun reportMessage(id: String)
309
    }
310
}