Unverified Commit 1fd253cc authored by Filipe Brito's avatar Filipe Brito Committed by GitHub

Merge pull request #2287 from RobotJINI/develop

[IMPROVEMENT] Immediate message loading
parents 475f195e ccec99f5
...@@ -117,7 +117,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -117,7 +117,8 @@ class ChatRoomPresenter @Inject constructor(
private var chatRoomId: String? = null private var chatRoomId: String? = null
private lateinit var chatRoomType: String private lateinit var chatRoomType: String
private var chatIsBroadcast: Boolean = false private lateinit var chatRoomName: String
private var isBroadcast: Boolean = false
private var chatRoles = emptyList<ChatRoomRole>() private var chatRoles = emptyList<ChatRoomRole>()
private val stateChannel = Channel<State>() private val stateChannel = Channel<State>()
private var typingStatusSubscriptionId: String? = null private var typingStatusSubscriptionId: String? = null
...@@ -136,56 +137,91 @@ class ChatRoomPresenter @Inject constructor( ...@@ -136,56 +137,91 @@ class ChatRoomPresenter @Inject constructor(
draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId" draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId"
chatRoomId = roomId chatRoomId = roomId
chatRoomType = roomType chatRoomType = roomType
chatRoomName = roomName
chatRoles = emptyList()
var canModerate = isOwnerOrMod()
GlobalScope.launch(Dispatchers.IO + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
try { // Can post anyway if has the 'post-readonly' permission on server.
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) { val room = dbManager.getRoom(roomId)
client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName) room?.let {
} else { isBroadcast = it.chatRoom.broadcast ?: false
emptyList() val roomUiModel = roomMapper.map(it, true)
} launchUI(strategy) {
} catch (ex: Exception) { view.onRoomUpdated(
Timber.e(ex) roomUiModel = roomUiModel.copy(
chatRoles = emptyList() broadcast = isBroadcast,
} finally {
// User has at least an 'owner' or 'moderator' role.
val canModerate = isOwnerOrMod()
// Can post anyway if has the 'post-readonly' permission on server.
val room = dbManager.getRoom(roomId)
room?.let {
chatIsBroadcast = it.chatRoom.broadcast ?: false
val roomUiModel = roomMapper.map(it, true)
launchUI(strategy) {
view.onRoomUpdated(roomUiModel = roomUiModel.copy(
broadcast = chatIsBroadcast,
canModerate = canModerate, canModerate = canModerate,
writable = roomUiModel.writable || canModerate writable = roomUiModel.writable || canModerate
)) )
} )
} }
}
loadMessages(roomId, roomType, clearDataSet = true) loadMessages(roomId, chatRoomType, clearDataSet = true)
chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } loadActiveMembers(roomId, chatRoomType, filterSelfOut = true)
?.let { messageId ->
val name = messageHelper.roomNameFromPermalink(chatRoomMessage) chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) }
citeMessage( ?.let { messageId ->
name!!, val name = messageHelper.roomNameFromPermalink(chatRoomMessage)
messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, citeMessage(
messageId, name!!,
true messageHelper.roomTypeFromPermalink(chatRoomMessage)!!,
) messageId,
} true
subscribeRoomChanges() )
}
/*FIXME: Get chat role can cause unresponsive problems especially on slower connections
We are updating the room again after the first step so that initial messages
get loaded in and the system appears more responsive. Something should be
done to either fix the load in speed of moderator roles or store the
information locally*/
if (getChatRole()) {
canModerate = isOwnerOrMod()
if (canModerate) {
//FIXME: add this in when moderator page is actually created
//view.updateModeration()
}
} }
subscribeRoomChanges()
} }
} }
private suspend fun getChatRole(): Boolean {
try {
if (roomTypeOf(chatRoomType) !is RoomType.DirectMessage) {
chatRoles = withContext(Dispatchers.IO + strategy.jobs) {
client.chatRoomRoles(
roomType = roomTypeOf(chatRoomType),
roomName = chatRoomName
)
}
return true
} else {
chatRoles = emptyList()
}
} catch (ex: Exception) {
Timber.e(ex)
chatRoles = emptyList()
}
return false
}
private suspend fun subscribeRoomChanges() { private suspend fun subscribeRoomChanges() {
withContext(Dispatchers.IO + strategy.jobs) { withContext(Dispatchers.IO + strategy.jobs) {
chatRoomId?.let { chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel) manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) { for (room in roomChangesChannel) {
dbManager.getRoom(room.id)?.let { chatRoom -> dbManager.getRoom(room.id)?.let { chatRoom ->
view.onRoomUpdated(roomMapper.map(chatRoom = chatRoom, showLastMessage = true)) view.onRoomUpdated(
roomMapper.map(
chatRoom = chatRoom,
showLastMessage = true
)
)
} }
} }
} }
...@@ -210,8 +246,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -210,8 +246,8 @@ class ChatRoomPresenter @Inject constructor(
) { ) {
this.chatRoomId = chatRoomId this.chatRoomId = chatRoomId
this.chatRoomType = chatRoomType this.chatRoomType = chatRoomType
launchUI(strategy) {
view.showLoading() GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
try { try {
if (offset == 0L) { if (offset == 0L) {
// FIXME - load just 50 messages from DB to speed up. We will reload from Network after that // FIXME - load just 50 messages from DB to speed up. We will reload from Network after that
...@@ -219,10 +255,10 @@ class ChatRoomPresenter @Inject constructor( ...@@ -219,10 +255,10 @@ class ChatRoomPresenter @Inject constructor(
val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50) val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50)
val oldMessages = mapper.map( val oldMessages = mapper.map(
localMessages, RoomUiModel( localMessages, RoomUiModel(
roles = chatRoles, roles = chatRoles,
// FIXME: Why are we fixing isRoom attribute to true here? // FIXME: Why are we fixing isRoom attribute to true here?
isBroadcast = chatIsBroadcast, isRoom = true isBroadcast = isBroadcast, isRoom = true
) )
) )
lastMessageId = localMessages.firstOrNull()?.id lastMessageId = localMessages.firstOrNull()?.id
val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
...@@ -248,8 +284,6 @@ class ChatRoomPresenter @Inject constructor( ...@@ -248,8 +284,6 @@ class ChatRoomPresenter @Inject constructor(
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally {
view.hideLoading()
} }
subscribeTypingStatus() subscribeTypingStatus()
...@@ -284,7 +318,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -284,7 +318,7 @@ class ChatRoomPresenter @Inject constructor(
view.showMessages( view.showMessages(
mapper.map( mapper.map(
messages, messages,
RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) RoomUiModel(roles = chatRoles, isBroadcast = isBroadcast, isRoom = true)
), ),
clearDataSet clearDataSet
) )
...@@ -300,7 +334,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -300,7 +334,7 @@ class ChatRoomPresenter @Inject constructor(
view.showSearchedMessages( view.showSearchedMessages(
mapper.map( mapper.map(
messages, messages,
RoomUiModel(chatRoles, chatIsBroadcast, true) RoomUiModel(chatRoles, isBroadcast, true)
) )
) )
} catch (ex: Exception) { } catch (ex: Exception) {
...@@ -332,7 +366,11 @@ class ChatRoomPresenter @Inject constructor( ...@@ -332,7 +366,11 @@ class ChatRoomPresenter @Inject constructor(
timestamp = Instant.now().toEpochMilli(), timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(user?.id, user?.username ?: username, user?.name), sender = SimpleUser(user?.id, user?.username ?: username, user?.name),
attachments = null, attachments = null,
avatar = currentServer.avatarUrl(username!!, token?.userId, token?.authToken), avatar = currentServer.avatarUrl(
username!!,
token?.userId,
token?.authToken
),
channels = null, channels = null,
editedAt = null, editedAt = null,
editedBy = null, editedBy = null,
...@@ -354,7 +392,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -354,7 +392,7 @@ class ChatRoomPresenter @Inject constructor(
view.showNewMessage( view.showNewMessage(
mapper.map( mapper.map(
newMessage, newMessage,
RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) RoomUiModel(roles = chatRoles, isBroadcast = isBroadcast)
), false ), false
) )
client.sendMessage(id, chatRoomId, text) client.sendMessage(id, chatRoomId, text)
...@@ -605,16 +643,21 @@ class ChatRoomPresenter @Inject constructor( ...@@ -605,16 +643,21 @@ 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, RoomUiModel( val models = mapper.map(
roles = chatRoles, messages.result, RoomUiModel(
isBroadcast = chatIsBroadcast, roles = chatRoles,
// FIXME: Why are we fixing isRoom attribute to true here? isBroadcast = isBroadcast,
isRoom = true // FIXME: Why are we fixing isRoom attribute to true here?
)) isRoom = true
)
)
messagesRepository.saveAll(messages.result) messagesRepository.saveAll(messages.result)
//if success - saving last synced time //if success - saving last synced time
//assume that BE returns ordered messages, the first message is the latest one //assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp) messagesRepository.saveLastSyncDate(
chatRoomId,
messages.result.first().timestamp
)
launchUI(strategy) { launchUI(strategy) {
view.showNewMessage(models, true) view.showNewMessage(models, true)
...@@ -691,9 +734,9 @@ class ChatRoomPresenter @Inject constructor( ...@@ -691,9 +734,9 @@ class ChatRoomPresenter @Inject constructor(
replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ",
quotedMessage = mapper.map( quotedMessage = mapper.map(
message, RoomUiModel( message, RoomUiModel(
roles = chatRoles, roles = chatRoles,
isBroadcast = chatIsBroadcast isBroadcast = isBroadcast
) )
).last().preview?.message ?: "" ).last().preview?.message ?: ""
) )
} }
...@@ -796,13 +839,15 @@ class ChatRoomPresenter @Inject constructor( ...@@ -796,13 +839,15 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
fun loadActiveMembers( suspend fun loadActiveMembers(
chatRoomId: String, chatRoomId: String,
chatRoomType: String, chatRoomType: String,
offset: Long = 0, offset: Long = 0,
filterSelfOut: Boolean = false filterSelfOut: Boolean = false
) { ) {
launchUI(strategy) { val activeUsers = mutableListOf<PeopleSuggestionUiModel>()
withContext(Dispatchers.IO + strategy.jobs) {
try { try {
val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") { val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") {
client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 50).result client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 50).result
...@@ -813,12 +858,12 @@ class ChatRoomPresenter @Inject constructor( ...@@ -813,12 +858,12 @@ class ChatRoomPresenter @Inject constructor(
// Take at most the 100 most recent messages distinguished by user. Can return less. // Take at most the 100 most recent messages distinguished by user. Can return less.
val recentMessages = messagesRepository.getRecentMessages(chatRoomId, 100) val recentMessages = messagesRepository.getRecentMessages(chatRoomId, 100)
.filterNot { filterSelfOut && it.sender?.username == self } .filterNot { filterSelfOut && it.sender?.username == self }
val activeUsers = mutableListOf<PeopleSuggestionUiModel>()
recentMessages.forEach { recentMessages.forEach {
val sender = it.sender val sender = it.sender
val username = sender?.username ?: "" val username = sender?.username ?: ""
val name = sender?.name ?: "" val name = sender?.name ?: ""
val avatarUrl = currentServer.avatarUrl(username, token?.userId, token?.authToken) val avatarUrl =
currentServer.avatarUrl(username, token?.userId, token?.authToken)
val found = members.firstOrNull { member -> member.username == username } val found = members.firstOrNull { member -> member.username == username }
val status = if (found != null) found.status else UserStatus.Offline() val status = if (found != null) found.status else UserStatus.Offline()
val searchList = mutableListOf(username, name) val searchList = mutableListOf(username, name)
...@@ -839,7 +884,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -839,7 +884,8 @@ class ChatRoomPresenter @Inject constructor(
activeUsers.addAll(others.map { activeUsers.addAll(others.map {
val username = it.username ?: "" val username = it.username ?: ""
val name = it.name ?: "" val name = it.name ?: ""
val avatarUrl = currentServer.avatarUrl(username, token?.userId, token?.authToken) val avatarUrl =
currentServer.avatarUrl(username, token?.userId, token?.authToken)
val searchList = mutableListOf(username, name) val searchList = mutableListOf(username, name)
PeopleSuggestionUiModel( PeopleSuggestionUiModel(
avatarUrl, avatarUrl,
...@@ -852,11 +898,13 @@ class ChatRoomPresenter @Inject constructor( ...@@ -852,11 +898,13 @@ class ChatRoomPresenter @Inject constructor(
) )
}) })
view.populatePeopleSuggestions(activeUsers)
} catch (e: RocketChatException) { } catch (e: RocketChatException) {
Timber.e(e) Timber.e(e)
} }
} }
launchUI(strategy) {
view.populatePeopleSuggestions(activeUsers)
}
} }
fun spotlight(query: String, @AutoCompleteType type: Int, filterSelfOut: Boolean = false) { fun spotlight(query: String, @AutoCompleteType type: Int, filterSelfOut: Boolean = false) {
...@@ -969,48 +1017,49 @@ class ChatRoomPresenter @Inject constructor( ...@@ -969,48 +1017,49 @@ class ChatRoomPresenter @Inject constructor(
} }
// TODO: move this to new interactor or FetchChatRoomsInteractor? // TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(Dispatchers.IO) { private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> =
retryDB("getAllSync()") { withContext(Dispatchers.IO) {
dbManager.chatRoomDao().getAllSync().filter { retryDB("getAllSync()") {
if (name == null) { dbManager.chatRoomDao().getAllSync().filter {
return@filter true if (name == null) {
} return@filter true
it.chatRoom.name == name || it.chatRoom.fullname == name }
}.map { it.chatRoom.name == name || it.chatRoom.fullname == name
with(it.chatRoom) { }.map {
ChatRoom( with(it.chatRoom) {
id = id, ChatRoom(
subscriptionId = subscriptionId, id = id,
parentId = parentId, subscriptionId = subscriptionId,
type = roomTypeOf(type), parentId = parentId,
unread = unread, type = roomTypeOf(type),
broadcast = broadcast ?: false, unread = unread,
alert = alert, broadcast = broadcast ?: false,
fullName = fullname, alert = alert,
name = name ?: "", fullName = fullname,
favorite = favorite ?: false, name = name ?: "",
default = isDefault ?: false, favorite = favorite ?: false,
readonly = readonly, default = isDefault ?: false,
open = open, readonly = readonly,
lastMessage = null, open = open,
archived = false, lastMessage = null,
status = null, archived = false,
user = null, status = null,
userMentions = userMentions, user = null,
client = client, userMentions = userMentions,
announcement = null, client = client,
description = null, announcement = null,
groupMentions = groupMentions, description = null,
roles = null, groupMentions = groupMentions,
topic = null, roles = null,
lastSeen = this.lastSeen, topic = null,
timestamp = timestamp, lastSeen = this.lastSeen,
updatedAt = updatedAt timestamp = timestamp,
) updatedAt = updatedAt
)
}
} }
} }
} }
}
fun joinChat(chatRoomId: String) { fun joinChat(chatRoomId: String) {
launchUI(strategy) { launchUI(strategy) {
...@@ -1019,7 +1068,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1019,7 +1068,8 @@ class ChatRoomPresenter @Inject constructor(
val canPost = permissions.canPostToReadOnlyChannels() val canPost = permissions.canPostToReadOnlyChannels()
dbManager.getRoom(chatRoomId)?.let { dbManager.getRoom(chatRoomId)?.let {
val roomUiModel = roomMapper.map(it, true).copy( val roomUiModel = roomMapper.map(it, true).copy(
writable = canPost) writable = canPost
)
view.onJoined(roomUiModel = roomUiModel) view.onJoined(roomUiModel = roomUiModel)
view.onRoomUpdated(roomUiModel = roomUiModel) view.onRoomUpdated(roomUiModel = roomUiModel)
} }
...@@ -1280,8 +1330,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1280,8 +1330,8 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
val viewModelStreamedMessage = mapper.map( val viewModelStreamedMessage = mapper.map(
streamedMessage, RoomUiModel( streamedMessage, RoomUiModel(
roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true roles = chatRoles, isBroadcast = isBroadcast, isRoom = true
) )
) )
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 }
......
...@@ -429,25 +429,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -429,25 +429,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
if (recycler_view.adapter == null) {
recycler_view.adapter = adapter
if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener)
}
recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(onScrollListener)
// Load just once, on the first page...
presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true)
}
val oldMessagesCount = adapter.itemCount val oldMessagesCount = adapter.itemCount
adapter.appendData(dataSet) adapter.appendData(dataSet)
if (oldMessagesCount == 0 && dataSet.isNotEmpty()) { if (oldMessagesCount == 0 && dataSet.isNotEmpty()) {
recycler_view.scrollToPosition(0) recycler_view.scrollToPosition(0)
verticalScrollOffset.set(0) verticalScrollOffset.set(0)
} }
presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true)
empty_chat_view.isVisible = adapter.itemCount == 0 empty_chat_view.isVisible = adapter.itemCount == 0
dismissEmojiKeyboard() dismissEmojiKeyboard()
} }
...@@ -469,10 +456,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -469,10 +456,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupMessageComposer(roomUiModel) setupMessageComposer(roomUiModel)
isBroadcastChannel = roomUiModel.broadcast isBroadcastChannel = roomUiModel.broadcast
isFavorite = roomUiModel.favorite.orFalse() isFavorite = roomUiModel.favorite.orFalse()
if (isBroadcastChannel && !roomUiModel.canModerate) { disableMenu = (roomUiModel.broadcast && !roomUiModel.canModerate)
disableMenu = true activity?.invalidateOptionsMenu()
activity?.invalidateOptionsMenu()
}
} }
} }
...@@ -807,7 +792,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -807,7 +792,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.loadMessages(chatRoomId, chatRoomType, page * 30L) presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
} }
} }
recycler_view.adapter = adapter
recycler_view.addOnScrollListener(fabScrollListener) recycler_view.addOnScrollListener(fabScrollListener)
recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener)
recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(onScrollListener)
} }
private fun setupFab() { private fun setupFab() {
......
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