Unverified Commit 774fdca7 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge pull request #701 from RocketChat/feature-2.x/stream-room-messages

[NEW] stream room messages
parents 54afdeb2 caf38042
...@@ -6,8 +6,14 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -6,8 +6,14 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.BaseRoom
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.unsubscibre
import chat.rocket.core.internal.rest.messages import chat.rocket.core.internal.rest.messages
import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
...@@ -15,14 +21,20 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -15,14 +21,20 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
private val client = factory.create(serverInteractor.get()!!) private val client = factory.create(serverInteractor.get()!!)
private val roomMessages = ArrayList<Message>()
private var subId: String? = null
fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Int = 0) { fun loadMessages(chatRoomId: String, chatRoomType: String, offset: Int = 0) {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val messages = client.messages(chatRoomId, BaseRoom.RoomType.valueOf(chatRoomType), offset.toLong(), 30).result val messages = client.messages(chatRoomId, BaseRoom.RoomType.valueOf(chatRoomType), offset.toLong(), 30).result
view.showMessages(messages.toMutableList(), serverInteractor.get()!!) synchronized(roomMessages) {
roomMessages.addAll(messages)
}
view.showMessages(messages, serverInteractor.get()!!)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace()
ex.message?.let { ex.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
...@@ -36,15 +48,66 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -36,15 +48,66 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
fun sendMessage(chatRoomId: String, text: String) { fun sendMessage(chatRoomId: String, text: String) {
launchUI(strategy) { launchUI(strategy) {
view.disableMessageInput()
try { try {
val message = client.sendMessage(chatRoomId, text) val message = client.sendMessage(chatRoomId, text)
view.showSentMessage(message) // ignore message for now, will receive it on the stream
view.enableMessageInput(clear = true)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace()
ex.message?.let { ex.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
view.enableMessageInput()
}
}
}
fun subscribeMessages(roomId: String) {
launchUI(strategy) {
subId = client.subscribeRoomMessages(roomId) {
Timber.d("subscribe messages for $roomId: $it")
}
listenMessages(roomId)
}
}
fun unsubscribeMessages() {
launch(CommonPool) {
subId?.let { subscriptionId ->
client.unsubscibre(subscriptionId)
}
}
}
private suspend fun listenMessages(roomId: String) {
launch(CommonPool + strategy.jobs) {
for (message in client.messagesChannel) {
if (message.roomId != roomId) {
Timber.d("Ignoring message for room ${message.roomId}, expecting $roomId")
}
updateMessage(message)
}
}
}
private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) {
synchronized(roomMessages) {
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index != -1) {
Timber.d("Updatind message at $index")
roomMessages[index] = streamedMessage
view.dispatchUpdateMessage(index, streamedMessage)
} else {
Timber.d("Adding new message")
roomMessages.add(0, streamedMessage)
view.showNewMessage(streamedMessage)
}
} }
} }
} }
......
...@@ -12,7 +12,7 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -12,7 +12,7 @@ interface ChatRoomView : LoadingView, MessageView {
* @param dataSet The data set to show. * @param dataSet The data set to show.
* @param serverUrl The server URL. * @param serverUrl The server URL.
*/ */
fun showMessages(dataSet: MutableList<Message>, serverUrl: String) fun showMessages(dataSet: List<Message>, serverUrl: String)
/** /**
* Send a message to a chat room. * Send a message to a chat room.
...@@ -23,8 +23,19 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -23,8 +23,19 @@ interface ChatRoomView : LoadingView, MessageView {
/** /**
* Shows a (recent) message sent to a chat room. * Shows a (recent) message sent to a chat room.
*
* @param message The (recent) message sent to a chat room. * @param message The (recent) message sent to a chat room.
*/ */
fun showSentMessage(message: Message) fun showNewMessage(message: Message)
/**
* Dispatch a update to the recycler views adapter about a changed message.
*
* @param index The index of the changed message
*/
fun dispatchUpdateMessage(index: Int, message: Message)
fun disableMessageInput()
fun enableMessageInput(clear: Boolean = false)
} }
\ No newline at end of file
...@@ -37,11 +37,10 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -37,11 +37,10 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
private var isChatRoomReadOnly: Boolean = false private var isChatRoomReadOnly: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room) setContentView(R.layout.activity_chat_room)
AndroidInjection.inject(this)
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" } requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
......
...@@ -19,13 +19,22 @@ import kotlinx.android.synthetic.main.avatar.view.* ...@@ -19,13 +19,22 @@ import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.* import kotlinx.android.synthetic.main.item_message.view.*
class ChatRoomAdapter(private val context: Context, class ChatRoomAdapter(private val context: Context,
private var dataSet: MutableList<Message>,
private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() { private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
init {
setHasStableIds(true)
}
val dataSet = ArrayList<Message>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_message)) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_message))
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position]) override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
onBindViewHolder(holder, position)
}
override fun getItemCount(): Int = dataSet.size override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position override fun getItemViewType(position: Int): Int = position
...@@ -41,6 +50,15 @@ class ChatRoomAdapter(private val context: Context, ...@@ -41,6 +50,15 @@ class ChatRoomAdapter(private val context: Context,
notifyItemInserted(0) notifyItemInserted(0)
} }
fun updateItem(index: Int, message: Message) {
dataSet[index] = message
notifyItemChanged(index)
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.hashCode().toLong()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(message: Message) = with(itemView) { fun bind(message: Message) = with(itemView) {
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.ui ...@@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
...@@ -38,6 +39,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" ...@@ -38,6 +39,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only" private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
class ChatRoomFragment : Fragment(), ChatRoomView { class ChatRoomFragment : Fragment(), ChatRoomView {
@Inject lateinit var presenter: ChatRoomPresenter @Inject lateinit var presenter: ChatRoomPresenter
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var chatRoomName: String private lateinit var chatRoomName: String
...@@ -65,16 +67,23 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -65,16 +67,23 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.loadMessages(chatRoomId, chatRoomType) presenter.loadMessages(chatRoomId, chatRoomType)
presenter.subscribeMessages(chatRoomId)
setupComposer() setupComposer()
} }
override fun showMessages(dataSet: MutableList<Message>, serverUrl: String) { override fun onDestroyView() {
presenter.unsubscribeMessages()
super.onDestroyView()
}
override fun showMessages(dataSet: List<Message>, serverUrl: String) {
activity?.apply { activity?.apply {
if (recycler_view.adapter == null) { if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(this, dataSet, serverUrl) adapter = ChatRoomAdapter(this, serverUrl)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
recycler_view.layoutManager = linearLayoutManager recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
if (dataSet.size >= 30) { if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) { recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) { override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
...@@ -82,9 +91,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -82,9 +91,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
} }
}) })
} }
} else {
adapter.addDataSet(dataSet)
} }
adapter.addDataSet(dataSet)
} }
} }
...@@ -94,12 +103,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView { ...@@ -94,12 +103,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
} }
} }
override fun showSentMessage(message: Message) { override fun showNewMessage(message: Message) {
text_message.textContent = "" text_message.textContent = ""
adapter.addItem(message) adapter.addItem(message)
recycler_view.smoothScrollToPosition(0) recycler_view.smoothScrollToPosition(0)
} }
override fun disableMessageInput() {
text_send.isEnabled = false
text_message.isEnabled = false
}
override fun enableMessageInput(clear: Boolean) {
text_send.isEnabled = true
text_message.isEnabled = true
if (clear) text_message.textContent = ""
}
override fun dispatchUpdateMessage(index: Int, message: Message) {
adapter.updateItem(index, message)
}
override fun showLoading() = view_loading.setVisibility(true) override fun showLoading() = view_loading.setVisibility(true)
override fun hideLoading() = view_loading.setVisibility(false) override fun hideLoading() = view_loading.setVisibility(false)
......
...@@ -87,8 +87,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -87,8 +87,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
State.Authenticating -> Timber.d("Authenticating") State.Authenticating -> Timber.d("Authenticating")
State.Connected -> { State.Connected -> {
Timber.d("Connected") Timber.d("Connected")
client.subscribeSubscriptions() client.subscribeSubscriptions {
client.subscribeRooms() Timber.d("subscriptions: $it")
}
client.subscribeRooms {
Timber.d("rooms: $it")
}
} }
} }
} }
......
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