From 152ff83fa01d16d3ec168ee74e77529f2419cf2a Mon Sep 17 00:00:00 2001 From: Asel Peiris Date: Sun, 10 Mar 2019 22:58:44 +0530 Subject: [PATCH 1/2] Highlight the searched word in the result. --- .../chatroom/adapter/ChatRoomAdapter.kt | 44 +- .../chatroom/adapter/MessageViewHolder.kt | 40 +- .../presentation/ChatRoomPresenter.kt | 445 +++++++++--------- .../chatroom/presentation/ChatRoomView.kt | 2 +- .../android/chatroom/ui/ChatRoomFragment.kt | 210 +++++---- .../chatroom/uimodel/AttachmentUiModel.kt | 3 + .../android/chatroom/uimodel/BaseUiModel.kt | 2 + .../chatroom/uimodel/MessageReplyUiModel.kt | 4 +- .../chatroom/uimodel/MessageUiModel.kt | 41 +- .../chatroom/uimodel/UrlPreviewUiModel.kt | 34 +- 10 files changed, 430 insertions(+), 395 deletions(-) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt index 1bd1efccae..7aae195063 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt @@ -23,13 +23,13 @@ import timber.log.Timber import java.security.InvalidParameterException class ChatRoomAdapter( - private val roomId: String? = null, - private val roomType: String? = null, - private val roomName: String? = null, - private val actionSelectListener: OnActionSelected? = null, - private val enableActions: Boolean = true, - private val reactionListener: EmojiReactionListener? = null, - private val navigator: ChatRoomNavigator? = null + private val roomId: String? = null, + private val roomType: String? = null, + private val roomName: String? = null, + private val actionSelectListener: OnActionSelected? = null, + private val enableActions: Boolean = true, + private val reactionListener: EmojiReactionListener? = null, + private val navigator: ChatRoomNavigator? = null ) : RecyclerView.Adapter>() { private val dataSet = ArrayList>() @@ -42,9 +42,9 @@ class ChatRoomAdapter( BaseUiModel.ViewType.MESSAGE -> { val view = parent.inflate(R.layout.item_message) MessageViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + reactionListener ) { userId -> navigator?.toUserDetails(userId) } } BaseUiModel.ViewType.URL_PREVIEW -> { @@ -54,18 +54,18 @@ class ChatRoomAdapter( BaseUiModel.ViewType.ATTACHMENT -> { val view = parent.inflate(R.layout.item_message_attachment) AttachmentViewHolder( - view, - actionsListener, - reactionListener, - actionAttachmentOnClickListener + view, + actionsListener, + reactionListener, + actionAttachmentOnClickListener ) } BaseUiModel.ViewType.MESSAGE_REPLY -> { val view = parent.inflate(R.layout.item_message_reply) MessageReplyViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + reactionListener ) { roomName, permalink -> actionSelectListener?.openDirectMessage(roomName, permalink) } @@ -138,7 +138,7 @@ class ChatRoomAdapter( //---At first we will update all already saved elements with received updated ones val filteredDataSet = dataSet.filter { newItem -> val matchedIndex = - this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } + this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } if (matchedIndex > -1) { this.dataSet[matchedIndex] = newItem notifyItemChanged(matchedIndex) @@ -288,10 +288,10 @@ class ChatRoomAdapter( fun showMessageInfo(id: String) fun citeMessage( - roomName: String, - roomType: String, - messageId: String, - mentionAuthor: Boolean + roomName: String, + roomType: String, + messageId: String, + mentionAuthor: Boolean ) fun copyMessage(id: String) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt index 22a83afc43..e207fe4688 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt @@ -1,10 +1,15 @@ package chat.rocket.android.chatroom.adapter import android.graphics.Color +import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable +import android.text.SpannableString import android.text.method.LinkMovementMethod +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan import android.text.style.ImageSpan +import android.text.style.StyleSpan import android.view.View import androidx.core.view.isVisible import chat.rocket.android.R @@ -16,10 +21,10 @@ import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.item_message.view.* class MessageViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null, - private val avatarListener: (String) -> Unit + itemView: View, + listener: ActionsListener, + reactionListener: EmojiReactionListener? = null, + private val avatarListener: (String) -> Unit ) : BaseViewHolder(itemView, listener, reactionListener), Drawable.Callback { init { @@ -49,7 +54,22 @@ class MessageViewHolder( } } - text_content.text_content.text = data.content + if (data.isSearchResult) { + fun setHighlightSpaannable(myText: CharSequence): SpannableString { + var spannableContent = SpannableString(myText) + val searchTermStart = data.content.indexOf(data.searchTerm) + val highlightColor = BackgroundColorSpan(Color.rgb(255, 255, 0)) + if (searchTermStart > 0) { + spannableContent.setSpan(highlightColor, searchTermStart, searchTermStart + data.searchTerm.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE) + } + return spannableContent + } + + val myText = data.content + text_content.text_content.text = setHighlightSpaannable(myText) + } else { + text_content.text_content.text = data.content + } image_avatar.setImageURI(data.avatar) text_content.setTextColor(if (data.isTemporary) Color.GRAY else Color.BLACK) @@ -63,11 +83,11 @@ class MessageViewHolder( read_receipt_view.isVisible = false } else { read_receipt_view.setImageResource( - if (data.unread == true) { - R.drawable.ic_check_unread_24dp - } else { - R.drawable.ic_check_read_24dp - } + if (data.unread == true) { + R.drawable.ic_check_unread_24dp + } else { + R.drawable.ic_check_read_24dp + } ) read_receipt_view.isVisible = true } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 541e4cd51a..57b2850906 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -88,24 +88,24 @@ import java.util.* import javax.inject.Inject class ChatRoomPresenter @Inject constructor( - private val view: ChatRoomView, - private val navigator: ChatRoomNavigator, - private val strategy: CancelStrategy, - private val permissions: PermissionsInteractor, - private val uriInteractor: UriInteractor, - private val messagesRepository: MessagesRepository, - private val usersRepository: UsersRepository, - private val localRepository: LocalRepository, - private val analyticsManager: AnalyticsManager, - private val userHelper: UserHelper, - private val mapper: UiModelMapper, - private val roomMapper: RoomUiModelMapper, - private val jobSchedulerInteractor: JobSchedulerInteractor, - private val messageHelper: MessageHelper, - private val dbManager: DatabaseManager, - getSettingsInteractor: GetSettingsInteractor, - serverInteractor: GetCurrentServerInteractor, - factory: ConnectionManagerFactory + private val view: ChatRoomView, + private val navigator: ChatRoomNavigator, + private val strategy: CancelStrategy, + private val permissions: PermissionsInteractor, + private val uriInteractor: UriInteractor, + private val messagesRepository: MessagesRepository, + private val usersRepository: UsersRepository, + private val localRepository: LocalRepository, + private val analyticsManager: AnalyticsManager, + private val userHelper: UserHelper, + private val mapper: UiModelMapper, + private val roomMapper: RoomUiModelMapper, + private val jobSchedulerInteractor: JobSchedulerInteractor, + private val messageHelper: MessageHelper, + private val dbManager: DatabaseManager, + getSettingsInteractor: GetSettingsInteractor, + serverInteractor: GetCurrentServerInteractor, + factory: ConnectionManagerFactory ) { private val currentServer = serverInteractor.get()!! private val manager = factory.create(currentServer) @@ -126,10 +126,10 @@ class ChatRoomPresenter @Inject constructor( private lateinit var draftKey: String fun setupChatRoom( - roomId: String, - roomName: String, - roomType: String, - chatRoomMessage: String? = null + roomId: String, + roomName: String, + roomType: String, + chatRoomMessage: String? = null ) { draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId" chatRoomId = roomId @@ -154,24 +154,24 @@ class ChatRoomPresenter @Inject constructor( val roomUiModel = roomMapper.map(it, true) launchUI(strategy) { view.onRoomUpdated(roomUiModel = roomUiModel.copy( - broadcast = chatIsBroadcast, - canModerate = canModerate, - writable = roomUiModel.writable || canModerate + broadcast = chatIsBroadcast, + canModerate = canModerate, + writable = roomUiModel.writable || canModerate )) } } loadMessages(roomId, roomType, clearDataSet = true) chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } - ?.let { messageId -> - val name = messageHelper.roomNameFromPermalink(chatRoomMessage) - citeMessage( - name!!, - messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, - messageId, - true - ) - } + ?.let { messageId -> + val name = messageHelper.roomNameFromPermalink(chatRoomMessage) + citeMessage( + name!!, + messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, + messageId, + true + ) + } subscribeRoomChanges() } } @@ -201,10 +201,10 @@ class ChatRoomPresenter @Inject constructor( } fun loadMessages( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - clearDataSet: Boolean = false + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + clearDataSet: Boolean = false ) { this.chatRoomId = chatRoomId this.chatRoomType = chatRoomType @@ -216,10 +216,10 @@ class ChatRoomPresenter @Inject constructor( // FIXME - We need to handle the pagination, first fetch from DB, then from network val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50) val oldMessages = mapper.map( - localMessages, RoomUiModel( - roles = chatRoles, - // FIXME: Why are we fixing isRoom attribute to true here? - isBroadcast = chatIsBroadcast, isRoom = true + localMessages, RoomUiModel( + roles = chatRoles, + // FIXME: Why are we fixing isRoom attribute to true here? + isBroadcast = chatIsBroadcast, isRoom = true ) ) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) @@ -255,15 +255,15 @@ class ChatRoomPresenter @Inject constructor( } private suspend fun loadAndShowMessages( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - clearDataSet: Boolean + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + clearDataSet: Boolean ) { val messages = - retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") { - client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result - } + retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") { + client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result + } messagesRepository.saveAll(messages) //we are saving last sync date of latest synced chat room message @@ -279,11 +279,11 @@ class ChatRoomPresenter @Inject constructor( } view.showMessages( - mapper.map( - messages, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) - ), - clearDataSet + mapper.map( + messages, + RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) + ), + clearDataSet ) } @@ -295,10 +295,10 @@ class ChatRoomPresenter @Inject constructor( client.searchMessages(chatRoomId, searchText).result } view.showSearchedMessages( - mapper.map( - messages, - RoomUiModel(chatRoles, chatIsBroadcast, true) - ) + mapper.map( + messages, + RoomUiModel(chatRoles, chatIsBroadcast, true) + ), searchText ) } catch (ex: Exception) { Timber.e(ex) @@ -322,36 +322,36 @@ class ChatRoomPresenter @Inject constructor( val id = UUID.randomUUID().toString() val username = userHelper.username() val newMessage = Message( - id = id, - roomId = chatRoomId, - message = text, - timestamp = Instant.now().toEpochMilli(), - sender = SimpleUser(null, username, username), - attachments = null, - avatar = currentServer.avatarUrl(username ?: ""), - channels = null, - editedAt = null, - editedBy = null, - groupable = false, - parseUrls = false, - pinned = false, - starred = emptyList(), - mentions = emptyList(), - reactions = null, - senderAlias = null, - type = null, - updatedAt = null, - urls = null, - synced = false, - unread = true + id = id, + roomId = chatRoomId, + message = text, + timestamp = Instant.now().toEpochMilli(), + sender = SimpleUser(null, username, username), + attachments = null, + avatar = currentServer.avatarUrl(username ?: ""), + channels = null, + editedAt = null, + editedBy = null, + groupable = false, + parseUrls = false, + pinned = false, + starred = emptyList(), + mentions = emptyList(), + reactions = null, + senderAlias = null, + type = null, + updatedAt = null, + urls = null, + synced = false, + unread = true ) try { messagesRepository.save(newMessage) view.showNewMessage( - mapper.map( - newMessage, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) - ), false + mapper.map( + newMessage, + RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) + ), false ) client.sendMessage(id, chatRoomId, text) messagesRepository.save(newMessage.copy(synced = true)) @@ -410,14 +410,14 @@ class ChatRoomPresenter @Inject constructor( view.showInvalidFileMessage() } else { val byteArray = - bitmap.getByteArray(mimeType, 100, settings.uploadMaxFileSize()) + bitmap.getByteArray(mimeType, 100, settings.uploadMaxFileSize()) retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { byteArray.inputStream() } @@ -454,11 +454,11 @@ class ChatRoomPresenter @Inject constructor( else -> { retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { uriInteractor.getInputStream(uri) } @@ -495,11 +495,11 @@ class ChatRoomPresenter @Inject constructor( else -> { retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { byteArray.inputStream() } @@ -591,20 +591,20 @@ class ChatRoomPresenter @Inject constructor( // try { val messages = - retryIO(description = "history($chatRoomId, $roomType, $instant)") { - client.history( - chatRoomId, roomType, count = 50, - oldest = instant - ) - } + retryIO(description = "history($chatRoomId, $roomType, $instant)") { + client.history( + chatRoomId, roomType, count = 50, + oldest = instant + ) + } Timber.d("History: $messages") if (messages.result.isNotEmpty()) { val models = mapper.map(messages.result, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast, - // FIXME: Why are we fixing isRoom attribute to true here? - isRoom = true + roles = chatRoles, + isBroadcast = chatIsBroadcast, + // FIXME: Why are we fixing isRoom attribute to true here? + isRoom = true )) messagesRepository.saveAll(messages.result) //if success - saving last synced time @@ -673,7 +673,7 @@ class ChatRoomPresenter @Inject constructor( val username = msg.sender?.username ?: "" val mention = if (mentionAuthor && currentUsername != username) "@$username" else "" val room = - if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName + if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName val chatRoomType = when (roomTypeOf(roomType)) { is RoomType.DirectMessage -> "direct" is RoomType.PrivateGroup -> "group" @@ -682,14 +682,14 @@ class ChatRoomPresenter @Inject constructor( else -> "custom" } view.showReplyingAction( - username = getDisplayName(msg.sender), - replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", - quotedMessage = mapper.map( - message, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast - ) - ).last().preview?.message ?: "" + username = getDisplayName(msg.sender), + replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", + quotedMessage = mapper.map( + message, RoomUiModel( + roles = chatRoles, + isBroadcast = chatIsBroadcast + ) + ).last().preview?.message ?: "" ) } } @@ -792,10 +792,10 @@ class ChatRoomPresenter @Inject constructor( } fun loadActiveMembers( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - filterSelfOut: Boolean = false + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + filterSelfOut: Boolean = false ) { launchUI(strategy) { try { @@ -807,7 +807,7 @@ class ChatRoomPresenter @Inject constructor( val self = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) // Take at most the 100 most recent messages distinguished by user. Can return less. val recentMessages = messagesRepository.getRecentMessages(chatRoomId, 100) - .filterNot { filterSelfOut && it.sender?.username == self } + .filterNot { filterSelfOut && it.sender?.username == self } val activeUsers = mutableListOf() recentMessages.forEach { val sender = it.sender @@ -818,10 +818,10 @@ class ChatRoomPresenter @Inject constructor( val status = if (found != null) found.status else UserStatus.Offline() val searchList = mutableListOf(username, name) activeUsers.add( - PeopleSuggestionUiModel( - avatarUrl, username, username, name, status, - true, searchList - ) + PeopleSuggestionUiModel( + avatarUrl, username, username, name, status, + true, searchList + ) ) } // Filter out from members list the active users. @@ -837,13 +837,13 @@ class ChatRoomPresenter @Inject constructor( val avatarUrl = currentServer.avatarUrl(username) val searchList = mutableListOf(username, name) PeopleSuggestionUiModel( - avatarUrl, - username, - username, - name, - it.status, - true, - searchList + avatarUrl, + username, + username, + name, + it.status, + true, + searchList ) }) @@ -870,8 +870,8 @@ class ChatRoomPresenter @Inject constructor( val searchList = mutableListOf(username, name) it.emails?.forEach { email -> searchList.add(email.address) } PeopleSuggestionUiModel( - currentServer.avatarUrl(username), - username, username, name, it.status, false, searchList + currentServer.avatarUrl(username), + username, username, name, it.status, false, searchList ) }.filterNot { filterSelfOut && self != null && self == it.text }) } @@ -910,10 +910,10 @@ class ChatRoomPresenter @Inject constructor( } fun toChatDetails( - chatRoomId: String, - chatRoomType: String, - isSubscribed: Boolean, - isMenuDisabled: Boolean + chatRoomId: String, + chatRoomType: String, + isSubscribed: Boolean, + isMenuDisabled: Boolean ) { navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isMenuDisabled) } @@ -922,19 +922,19 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { try { val chatRooms = getChatRoomsAsync() - .filterNot { - it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat - } - .map { chatRoom -> - val name = chatRoom.name - val fullName = chatRoom.fullName ?: "" - ChatRoomSuggestionUiModel( - text = name, - name = name, - fullName = fullName, - searchList = listOf(name, fullName) - ) - } + .filterNot { + it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat + } + .map { chatRoom -> + val name = chatRoom.name + val fullName = chatRoom.fullName ?: "" + ChatRoomSuggestionUiModel( + text = name, + name = name, + fullName = fullName, + searchList = listOf(name, fullName) + ) + } view.populateRoomSuggestions(chatRooms) } catch (e: RocketChatException) { Timber.e(e) @@ -948,32 +948,32 @@ class ChatRoomPresenter @Inject constructor( dbManager.chatRoomDao().getSync(roomId)?.let { with(it.chatRoom) { ChatRoom( - id = id, - subscriptionId = subscriptionId, - type = roomTypeOf(type), - unread = unread, - broadcast = broadcast ?: false, - alert = alert, - fullName = fullname, - name = name, - favorite = favorite ?: false, - default = isDefault ?: false, - readonly = readonly, - open = open, - lastMessage = null, - archived = false, - status = null, - user = null, - userMentions = userMentions, - client = client, - announcement = null, - description = null, - groupMentions = groupMentions, - roles = null, - topic = null, - lastSeen = this.lastSeen, - timestamp = timestamp, - updatedAt = updatedAt + id = id, + subscriptionId = subscriptionId, + type = roomTypeOf(type), + unread = unread, + broadcast = broadcast ?: false, + alert = alert, + fullName = fullname, + name = name, + favorite = favorite ?: false, + default = isDefault ?: false, + readonly = readonly, + open = open, + lastMessage = null, + archived = false, + status = null, + user = null, + userMentions = userMentions, + client = client, + announcement = null, + description = null, + groupMentions = groupMentions, + roles = null, + topic = null, + lastSeen = this.lastSeen, + timestamp = timestamp, + updatedAt = updatedAt ) } } @@ -991,32 +991,32 @@ class ChatRoomPresenter @Inject constructor( }.map { with(it.chatRoom) { ChatRoom( - id = id, - subscriptionId = subscriptionId, - type = roomTypeOf(type), - unread = unread, - broadcast = broadcast ?: false, - alert = alert, - fullName = fullname, - name = name ?: "", - favorite = favorite ?: false, - default = isDefault ?: false, - readonly = readonly, - open = open, - lastMessage = null, - archived = false, - status = null, - user = null, - userMentions = userMentions, - client = client, - announcement = null, - description = null, - groupMentions = groupMentions, - roles = null, - topic = null, - lastSeen = this.lastSeen, - timestamp = timestamp, - updatedAt = updatedAt + id = id, + subscriptionId = subscriptionId, + type = roomTypeOf(type), + unread = unread, + broadcast = broadcast ?: false, + alert = alert, + fullName = fullname, + name = name ?: "", + favorite = favorite ?: false, + default = isDefault ?: false, + readonly = readonly, + open = open, + lastMessage = null, + archived = false, + status = null, + user = null, + userMentions = userMentions, + client = client, + announcement = null, + description = null, + groupMentions = groupMentions, + roles = null, + topic = null, + lastSeen = this.lastSeen, + timestamp = timestamp, + updatedAt = updatedAt ) } } @@ -1030,7 +1030,7 @@ class ChatRoomPresenter @Inject constructor( val canPost = permissions.canPostToReadOnlyChannels() dbManager.getRoom(chatRoomId)?.let { val roomUiModel = roomMapper.map(it, true).copy( - writable = canPost) + writable = canPost) view.onJoined(roomUiModel = roomUiModel) view.onRoomUpdated(roomUiModel = roomUiModel) } @@ -1047,15 +1047,15 @@ class ChatRoomPresenter @Inject constructor( if (it.isNotEmpty()) { if (it.first().type is RoomType.DirectMessage) { navigator.toDirectMessage( - chatRoomId = it.first().id, - chatRoomType = it.first().type.toString(), - chatRoomLastSeen = it.first().lastSeen ?: -1, - chatRoomName = roomName, - isChatRoomCreator = false, - isChatRoomFavorite = false, - isChatRoomReadOnly = false, - isChatRoomSubscribed = it.first().open, - chatRoomMessage = message + chatRoomId = it.first().id, + chatRoomType = it.first().type.toString(), + chatRoomLastSeen = it.first().lastSeen ?: -1, + chatRoomName = roomName, + isChatRoomCreator = false, + isChatRoomFavorite = false, + isChatRoomReadOnly = false, + isChatRoomSubscribed = it.first().open, + chatRoomMessage = message ) } else { throw IllegalStateException("Not a direct-message") @@ -1157,10 +1157,10 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { val emojiSuggestionUiModels = EmojiRepository.getAll().map { EmojiSuggestionUiModel( - text = it.shortname.replaceFirst(":", ""), - pinned = false, - emoji = it, - searchList = listOf(it.shortname) + text = it.shortname.replaceFirst(":", ""), + pinned = false, + emoji = it, + searchList = listOf(it.shortname) ) } view.populateEmojiSuggestions(emojis = emojiSuggestionUiModels) @@ -1264,9 +1264,9 @@ class ChatRoomPresenter @Inject constructor( private fun updateMessage(streamedMessage: Message) { launchUI(strategy) { val viewModelStreamedMessage = mapper.map( - streamedMessage, RoomUiModel( + streamedMessage, RoomUiModel( roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true - ) + ) ) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id } @@ -1302,6 +1302,7 @@ class ChatRoomPresenter @Inject constructor( fun clearDraftMessage() { localRepository.clear(draftKey) } + /** * Get unfinished message from local repository, when user left chat room without * sending a message and now the user is back. diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt index 15f241b2cb..8b725c0c5a 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt @@ -33,7 +33,7 @@ interface ChatRoomView : LoadingView, MessageView { * * @param dataSet The data set to show. */ - fun showSearchedMessages(dataSet: List>) + fun showSearchedMessages(dataSet: List>, searchTerm: String) /** * Send a message to a chat room. diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index ae386d9738..2ad8f0eeb2 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -6,12 +6,16 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.graphics.Typeface import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.os.Handler import android.provider.MediaStore +import android.text.Spannable +import android.text.SpannableString import android.text.SpannableStringBuilder +import android.text.style.StyleSpan import android.view.KeyEvent import android.view.LayoutInflater import android.view.Menu @@ -34,12 +38,7 @@ import androidx.recyclerview.widget.RecyclerView import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.chatroom.adapter.ChatRoomAdapter -import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter -import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter -import chat.rocket.android.chatroom.adapter.PEOPLE -import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter -import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter +import chat.rocket.android.chatroom.adapter.* import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.chatroom.presentation.ChatRoomPresenter import chat.rocket.android.chatroom.presentation.ChatRoomView @@ -102,15 +101,15 @@ import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject fun newInstance( - chatRoomId: String, - chatRoomName: String, - chatRoomType: String, - isReadOnly: Boolean, - chatRoomLastSeen: Long, - isSubscribed: Boolean = true, - isCreator: Boolean = false, - isFavorite: Boolean = false, - chatRoomMessage: String? = null + chatRoomId: String, + chatRoomName: String, + chatRoomType: String, + isReadOnly: Boolean, + chatRoomLastSeen: Long, + isSubscribed: Boolean = true, + isCreator: Boolean = false, + isFavorite: Boolean = false, + chatRoomMessage: String? = null ): Fragment { return ChatRoomFragment().apply { arguments = Bundle(1).apply { @@ -146,7 +145,7 @@ internal const val MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT = 1 internal const val MENU_ACTION_SHOW_DETAILS = 2 class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener, - ChatRoomAdapter.OnActionSelected, Drawable.Callback { + ChatRoomAdapter.OnActionSelected, Drawable.Callback { @Inject lateinit var presenter: ChatRoomPresenter @Inject @@ -183,14 +182,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR // For reveal and unreveal anim. private val hypotenuse by lazy { Math.hypot( - root_layout.width.toDouble(), - root_layout.height.toDouble() + root_layout.width.toDouble(), + root_layout.height.toDouble() ).toFloat() } private val max by lazy { Math.max( - layout_message_attachment_options.width.toDouble(), - layout_message_attachment_options.height.toDouble() + layout_message_attachment_options.width.toDouble(), + layout_message_attachment_options.height.toDouble() ).toFloat() } private val centerX by lazy { recycler_view.right } @@ -209,19 +208,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private var takenPhotoUri: Uri? = null private val layoutChangeListener = - View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom -> - val y = oldBottom - bottom - if (Math.abs(y) > 0 && isAdded) { - // if y is positive the keyboard is up else it's down - recycler_view.post { - if (y > 0 || Math.abs(verticalScrollOffset.get()) >= Math.abs(y)) { - ui { recycler_view.scrollBy(0, y) } - } else { - ui { recycler_view.scrollBy(0, verticalScrollOffset.get()) } + View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom -> + val y = oldBottom - bottom + if (Math.abs(y) > 0 && isAdded) { + // if y is positive the keyboard is up else it's down + recycler_view.post { + if (y > 0 || Math.abs(verticalScrollOffset.get()) >= Math.abs(y)) { + ui { recycler_view.scrollBy(0, y) } + } else { + ui { recycler_view.scrollBy(0, verticalScrollOffset.get()) } + } } } } - } private lateinit var endlessRecyclerViewScrollListener: EndlessRecyclerViewScrollListener @@ -291,9 +290,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? = container?.inflate(R.layout.fragment_chat_room) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -442,8 +441,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun showSearchedMessages(dataSet: List>) { + override fun showSearchedMessages(dataSet: List>, searchTerm: String) { recycler_view.removeOnScrollListener(endlessRecyclerViewScrollListener) + for (dataItem in dataSet) { + dataItem.isSearchResult = true + dataItem.searchTerm = searchTerm + } + adapter.clearData() adapter.prependData(dataSet) empty_chat_view.isVisible = adapter.itemCount == 0 @@ -481,15 +485,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ui { when (usernameList.size) { 1 -> text_typing_status.text = - SpannableStringBuilder() - .bold { append(usernameList[0]) } - .append(getString(R.string.msg_is_typing)) + SpannableStringBuilder() + .bold { append(usernameList[0]) } + .append(getString(R.string.msg_is_typing)) 2 -> text_typing_status.text = - SpannableStringBuilder() - .bold { append(usernameList[0]) } - .append(getString(R.string.msg_and)) - .bold { append(usernameList[1]) } - .append(getString(R.string.msg_are_typing)) + SpannableStringBuilder() + .bold { append(usernameList[0]) } + .append(getString(R.string.msg_and)) + .bold { append(usernameList[1]) } + .append(getString(R.string.msg_are_typing)) else -> text_typing_status.text = getString(R.string.msg_several_users_are_typing) } @@ -539,9 +543,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR text_count.text = "99+" } text_count.isVisible = true - } - - else if (!button_fab.isVisible) { + } else if (!button_fab.isVisible) { recycler_view.scrollToPosition(0) } verticalScrollOffset.set(0) @@ -682,8 +684,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List) { val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null) val dialog = AlertDialog.Builder(requireContext()) - .setView(layout) - .setCancelable(true) + .setView(layout) + .setCancelable(true) with(layout) { view_flipper.displayedChild = if (isCustom) 1 else 0 @@ -711,7 +713,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } text_view_usernames.text = requireContext().resources.getQuantityString( - R.plurals.msg_reacted_with_, usernames.size, listing, shortname) + R.plurals.msg_reacted_with_, usernames.size, listing, shortname) dialog.show() } @@ -792,7 +794,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR recycler_view.layoutManager = linearLayoutManager recycler_view.itemAnimator = DefaultItemAnimator() endlessRecyclerViewScrollListener = object : - EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) { + EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) { override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView) { presenter.loadMessages(chatRoomId, chatRoomType, page * 30L) } @@ -815,12 +817,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR text_room_is_read_only.isVisible = true input_container.isVisible = false text_room_is_read_only.setText( - if (isReadOnly) { - R.string.msg_this_room_is_read_only - } else { - // Not a read-only channel but user has been muted. - R.string.msg_muted_on_this_channel - } + if (isReadOnly) { + R.string.msg_this_room_is_read_only + } else { + // Not a read-only channel but user has been muted. + R.string.msg_muted_on_this_channel + } ) } else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) { input_container.isVisible = false @@ -832,18 +834,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR button_show_attachment_options.alpha = 1f activity?.supportFragmentManager?.registerFragmentLifecycleCallbacks( - object : FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentAttached( - fm: FragmentManager, - f: Fragment, - context: Context - ) { - if (f is MessageActionsBottomSheet) { - dismissEmojiKeyboard() + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentAttached( + fm: FragmentManager, + f: Fragment, + context: Context + ) { + if (f is MessageActionsBottomSheet) { + dismissEmojiKeyboard() + } } - } - }, - true + }, + true ) emojiKeyboardPopup = EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container)) @@ -933,7 +935,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR // Continue only if the File was successfully created photoFile?.also { takenPhotoUri = FileProvider.getUriForFile( - requireContext(), "chat.rocket.android.fileprovider", it + requireContext(), "chat.rocket.android.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, takenPhotoUri) startActivityForResult(takePictureIntent, REQUEST_CODE_FOR_PERFORM_CAMERA) @@ -950,27 +952,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun setupSuggestionsView() { suggestions_view.anchorTo(text_message) - .setMaximumHeight(resources.getDimensionPixelSize(R.dimen.suggestions_box_max_height)) - .addTokenAdapter(PeopleSuggestionsAdapter(context!!)) - .addTokenAdapter(CommandSuggestionsAdapter()) - .addTokenAdapter(RoomSuggestionsAdapter()) - .addTokenAdapter(EmojiSuggestionsAdapter()) - .addSuggestionProviderAction("@") { query -> - if (query.isNotEmpty()) { - presenter.spotlight(query, PEOPLE, true) + .setMaximumHeight(resources.getDimensionPixelSize(R.dimen.suggestions_box_max_height)) + .addTokenAdapter(PeopleSuggestionsAdapter(context!!)) + .addTokenAdapter(CommandSuggestionsAdapter()) + .addTokenAdapter(RoomSuggestionsAdapter()) + .addTokenAdapter(EmojiSuggestionsAdapter()) + .addSuggestionProviderAction("@") { query -> + if (query.isNotEmpty()) { + presenter.spotlight(query, PEOPLE, true) + } } - } - .addSuggestionProviderAction("#") { query -> - if (query.isNotEmpty()) { - presenter.loadChatRoomsSuggestions() + .addSuggestionProviderAction("#") { query -> + if (query.isNotEmpty()) { + presenter.loadChatRoomsSuggestions() + } + } + .addSuggestionProviderAction("/") { + presenter.loadCommands() + } + .addSuggestionProviderAction(":") { + presenter.loadEmojis() } - } - .addSuggestionProviderAction("/") { - presenter.loadCommands() - } - .addSuggestionProviderAction(":") { - presenter.loadEmojis() - } presenter.loadEmojis() presenter.loadCommands() @@ -999,8 +1001,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun subscribeComposeTextMessage() { text_message.asObservable().let { compositeDisposable.addAll( - subscribeComposeButtons(it), - subscribeComposeTypingStatus(it) + subscribeComposeButtons(it), + subscribeComposeTypingStatus(it) ) } } @@ -1015,8 +1017,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun subscribeComposeTypingStatus(observable: Observable): Disposable { return observable.debounce(300, TimeUnit.MILLISECONDS) - .skip(1) - .subscribe { t -> sendTypingStatus(t) } + .skip(1) + .subscribe { t -> sendTypingStatus(t) } } private fun setupComposeButtons(charSequence: CharSequence) { @@ -1082,10 +1084,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } override fun citeMessage( - roomName: String, - roomType: String, - messageId: String, - mentionAuthor: Boolean + roomName: String, + roomType: String, + messageId: String, + mentionAuthor: Boolean ) { presenter.citeMessage(roomName, roomType, messageId, mentionAuthor) } @@ -1118,15 +1120,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ui { val builder = AlertDialog.Builder(it) builder.setTitle(it.getString(R.string.msg_delete_message)) - .setMessage(it.getString(R.string.msg_delete_description)) - .setPositiveButton(it.getString(android.R.string.ok)) { _, _ -> - presenter.deleteMessage( - roomId, - id - ) - } - .setNegativeButton(it.getString(android.R.string.cancel)) { _, _ -> } - .show() + .setMessage(it.getString(R.string.msg_delete_description)) + .setPositiveButton(it.getString(android.R.string.ok)) { _, _ -> + presenter.deleteMessage( + roomId, + id + ) + } + .setNegativeButton(it.getString(android.R.string.cancel)) { _, _ -> } + .show() } } @@ -1148,7 +1150,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun reportMessage(id: String) { presenter.reportMessage(messageId = id, - description = "This message was reported by a user from the Android app") + description = "This message was reported by a user from the Android app") } fun openEmojiKeyboard() { diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt index e191978333..9925f5a307 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt @@ -18,6 +18,8 @@ data class AttachmentUiModel( override var showDayMarker: Boolean, override var menuItemsToHide: MutableList = mutableListOf(), override var permalink: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String = "", val id: Long, val title: CharSequence?, val description: CharSequence?, @@ -37,6 +39,7 @@ data class AttachmentUiModel( val fields: CharSequence?, val buttonAlignment: String?, val actions: List? + ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.ATTACHMENT.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt index 5198e6c946..befe955030 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt @@ -18,6 +18,8 @@ interface BaseUiModel { var showDayMarker: Boolean var menuItemsToHide: MutableList var permalink: String + var isSearchResult: Boolean + var searchTerm: String enum class ViewType(val viewType: Int) { MESSAGE(0), diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt index 347f814b24..93ae2a06d2 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt @@ -16,7 +16,9 @@ data class MessageReplyUiModel( override var menuItemsToHide: MutableList = mutableListOf(), override var currentDayMarkerText: String, override var showDayMarker: Boolean, - override var permalink: String + override var permalink: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String ="" ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.MESSAGE_REPLY.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt index f69ab559ac..03fb3677be 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt @@ -4,25 +4,28 @@ import chat.rocket.android.R import chat.rocket.core.model.Message data class MessageUiModel( - override val message: Message, - override val rawData: Message, - override val messageId: String, - override val avatar: String, - override val time: CharSequence, - override val senderName: CharSequence, - override val content: CharSequence, - override val isPinned: Boolean, - override var currentDayMarkerText: String, - override var showDayMarker: Boolean, - override var reactions: List, - override var nextDownStreamMessage: BaseUiModel<*>? = null, - override var preview: Message? = null, - override var unread: Boolean? = null, - var isFirstUnread: Boolean, - override var isTemporary: Boolean = false, - override var menuItemsToHide: MutableList = mutableListOf(), - override var permalink: String, - val subscriptionId: String + override val message: Message, + override val rawData: Message, + override val messageId: String, + override val avatar: String, + override val time: CharSequence, + override val senderName: CharSequence, + override val content: CharSequence, + override val isPinned: Boolean, + override var currentDayMarkerText: String, + override var showDayMarker: Boolean, + override var reactions: List, + override var nextDownStreamMessage: BaseUiModel<*>? = null, + override var preview: Message? = null, + override var unread: Boolean? = null, + var isFirstUnread: Boolean, + override var isTemporary: Boolean = false, + override var menuItemsToHide: MutableList = mutableListOf(), + override var permalink: String, + val subscriptionId: String, + override var isSearchResult:Boolean = false, + override var searchTerm: String = "" + ) : BaseMessageUiModel { override val viewType: Int get() = BaseUiModel.ViewType.MESSAGE.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt index 0efccafc66..f79d67c728 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt @@ -5,22 +5,24 @@ import chat.rocket.core.model.Message import chat.rocket.core.model.url.Url data class UrlPreviewUiModel( - override val message: Message, - override val rawData: Url, - override val messageId: String, - val title: CharSequence?, - val hostname: String, - val description: CharSequence?, - val thumbUrl: String?, - override var reactions: List, - override var nextDownStreamMessage: BaseUiModel<*>? = null, - override var preview: Message? = null, - override var isTemporary: Boolean = false, - override var unread: Boolean? = null, - override var menuItemsToHide: MutableList = mutableListOf(), - override var currentDayMarkerText: String, - override var showDayMarker: Boolean, - override var permalink: String + override val message: Message, + override val rawData: Url, + override val messageId: String, + val title: CharSequence?, + val hostname: String, + val description: CharSequence?, + val thumbUrl: String?, + override var reactions: List, + override var nextDownStreamMessage: BaseUiModel<*>? = null, + override var preview: Message? = null, + override var isTemporary: Boolean = false, + override var unread: Boolean? = null, + override var menuItemsToHide: MutableList = mutableListOf(), + override var currentDayMarkerText: String, + override var showDayMarker: Boolean, + override var permalink: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String = "" ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.URL_PREVIEW.viewType From e43f70aaa567859ccfce361d4d4344f588d490e5 Mon Sep 17 00:00:00 2001 From: Asel Peiris Date: Mon, 11 Mar 2019 06:56:33 +0530 Subject: [PATCH 2/2] Reformatting code --- .../chatroom/adapter/ChatRoomAdapter.kt | 44 +- .../chatroom/adapter/MessageViewHolder.kt | 25 +- .../presentation/ChatRoomPresenter.kt | 437 +++++++++--------- .../android/chatroom/ui/ChatRoomFragment.kt | 202 ++++---- .../chatroom/uimodel/AttachmentUiModel.kt | 67 ++- .../android/chatroom/uimodel/BaseUiModel.kt | 2 +- .../chatroom/uimodel/MessageReplyUiModel.kt | 2 +- .../chatroom/uimodel/MessageUiModel.kt | 42 +- .../chatroom/uimodel/UrlPreviewUiModel.kt | 36 +- 9 files changed, 438 insertions(+), 419 deletions(-) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt index 7aae195063..1bd1efccae 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt @@ -23,13 +23,13 @@ import timber.log.Timber import java.security.InvalidParameterException class ChatRoomAdapter( - private val roomId: String? = null, - private val roomType: String? = null, - private val roomName: String? = null, - private val actionSelectListener: OnActionSelected? = null, - private val enableActions: Boolean = true, - private val reactionListener: EmojiReactionListener? = null, - private val navigator: ChatRoomNavigator? = null + private val roomId: String? = null, + private val roomType: String? = null, + private val roomName: String? = null, + private val actionSelectListener: OnActionSelected? = null, + private val enableActions: Boolean = true, + private val reactionListener: EmojiReactionListener? = null, + private val navigator: ChatRoomNavigator? = null ) : RecyclerView.Adapter>() { private val dataSet = ArrayList>() @@ -42,9 +42,9 @@ class ChatRoomAdapter( BaseUiModel.ViewType.MESSAGE -> { val view = parent.inflate(R.layout.item_message) MessageViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + reactionListener ) { userId -> navigator?.toUserDetails(userId) } } BaseUiModel.ViewType.URL_PREVIEW -> { @@ -54,18 +54,18 @@ class ChatRoomAdapter( BaseUiModel.ViewType.ATTACHMENT -> { val view = parent.inflate(R.layout.item_message_attachment) AttachmentViewHolder( - view, - actionsListener, - reactionListener, - actionAttachmentOnClickListener + view, + actionsListener, + reactionListener, + actionAttachmentOnClickListener ) } BaseUiModel.ViewType.MESSAGE_REPLY -> { val view = parent.inflate(R.layout.item_message_reply) MessageReplyViewHolder( - view, - actionsListener, - reactionListener + view, + actionsListener, + reactionListener ) { roomName, permalink -> actionSelectListener?.openDirectMessage(roomName, permalink) } @@ -138,7 +138,7 @@ class ChatRoomAdapter( //---At first we will update all already saved elements with received updated ones val filteredDataSet = dataSet.filter { newItem -> val matchedIndex = - this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } + this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } if (matchedIndex > -1) { this.dataSet[matchedIndex] = newItem notifyItemChanged(matchedIndex) @@ -288,10 +288,10 @@ class ChatRoomAdapter( fun showMessageInfo(id: String) fun citeMessage( - roomName: String, - roomType: String, - messageId: String, - mentionAuthor: Boolean + roomName: String, + roomType: String, + messageId: String, + mentionAuthor: Boolean ) fun copyMessage(id: String) diff --git a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt index e207fe4688..93b0afbfb5 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt @@ -21,10 +21,10 @@ import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.item_message.view.* class MessageViewHolder( - itemView: View, - listener: ActionsListener, - reactionListener: EmojiReactionListener? = null, - private val avatarListener: (String) -> Unit + itemView: View, + listener: ActionsListener, + reactionListener: EmojiReactionListener? = null, + private val avatarListener: (String) -> Unit ) : BaseViewHolder(itemView, listener, reactionListener), Drawable.Callback { init { @@ -60,7 +60,12 @@ class MessageViewHolder( val searchTermStart = data.content.indexOf(data.searchTerm) val highlightColor = BackgroundColorSpan(Color.rgb(255, 255, 0)) if (searchTermStart > 0) { - spannableContent.setSpan(highlightColor, searchTermStart, searchTermStart + data.searchTerm.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE) + spannableContent.setSpan( + highlightColor, + searchTermStart, + searchTermStart + data.searchTerm.length, + Spannable.SPAN_INCLUSIVE_INCLUSIVE + ) } return spannableContent } @@ -83,11 +88,11 @@ class MessageViewHolder( read_receipt_view.isVisible = false } else { read_receipt_view.setImageResource( - if (data.unread == true) { - R.drawable.ic_check_unread_24dp - } else { - R.drawable.ic_check_read_24dp - } + if (data.unread == true) { + R.drawable.ic_check_unread_24dp + } else { + R.drawable.ic_check_read_24dp + } ) read_receipt_view.isVisible = true } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 57b2850906..94f8d4e668 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -88,24 +88,24 @@ import java.util.* import javax.inject.Inject class ChatRoomPresenter @Inject constructor( - private val view: ChatRoomView, - private val navigator: ChatRoomNavigator, - private val strategy: CancelStrategy, - private val permissions: PermissionsInteractor, - private val uriInteractor: UriInteractor, - private val messagesRepository: MessagesRepository, - private val usersRepository: UsersRepository, - private val localRepository: LocalRepository, - private val analyticsManager: AnalyticsManager, - private val userHelper: UserHelper, - private val mapper: UiModelMapper, - private val roomMapper: RoomUiModelMapper, - private val jobSchedulerInteractor: JobSchedulerInteractor, - private val messageHelper: MessageHelper, - private val dbManager: DatabaseManager, - getSettingsInteractor: GetSettingsInteractor, - serverInteractor: GetCurrentServerInteractor, - factory: ConnectionManagerFactory + private val view: ChatRoomView, + private val navigator: ChatRoomNavigator, + private val strategy: CancelStrategy, + private val permissions: PermissionsInteractor, + private val uriInteractor: UriInteractor, + private val messagesRepository: MessagesRepository, + private val usersRepository: UsersRepository, + private val localRepository: LocalRepository, + private val analyticsManager: AnalyticsManager, + private val userHelper: UserHelper, + private val mapper: UiModelMapper, + private val roomMapper: RoomUiModelMapper, + private val jobSchedulerInteractor: JobSchedulerInteractor, + private val messageHelper: MessageHelper, + private val dbManager: DatabaseManager, + getSettingsInteractor: GetSettingsInteractor, + serverInteractor: GetCurrentServerInteractor, + factory: ConnectionManagerFactory ) { private val currentServer = serverInteractor.get()!! private val manager = factory.create(currentServer) @@ -126,10 +126,10 @@ class ChatRoomPresenter @Inject constructor( private lateinit var draftKey: String fun setupChatRoom( - roomId: String, - roomName: String, - roomType: String, - chatRoomMessage: String? = null + roomId: String, + roomName: String, + roomType: String, + chatRoomMessage: String? = null ) { draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId" chatRoomId = roomId @@ -153,25 +153,27 @@ class ChatRoomPresenter @Inject constructor( chatIsBroadcast = it.chatRoom.broadcast ?: false val roomUiModel = roomMapper.map(it, true) launchUI(strategy) { - view.onRoomUpdated(roomUiModel = roomUiModel.copy( + view.onRoomUpdated( + roomUiModel = roomUiModel.copy( broadcast = chatIsBroadcast, canModerate = canModerate, writable = roomUiModel.writable || canModerate - )) + ) + ) } } loadMessages(roomId, roomType, clearDataSet = true) chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } - ?.let { messageId -> - val name = messageHelper.roomNameFromPermalink(chatRoomMessage) - citeMessage( - name!!, - messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, - messageId, - true - ) - } + ?.let { messageId -> + val name = messageHelper.roomNameFromPermalink(chatRoomMessage) + citeMessage( + name!!, + messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, + messageId, + true + ) + } subscribeRoomChanges() } } @@ -201,10 +203,10 @@ class ChatRoomPresenter @Inject constructor( } fun loadMessages( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - clearDataSet: Boolean = false + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + clearDataSet: Boolean = false ) { this.chatRoomId = chatRoomId this.chatRoomType = chatRoomType @@ -216,11 +218,11 @@ class ChatRoomPresenter @Inject constructor( // FIXME - We need to handle the pagination, first fetch from DB, then from network val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50) val oldMessages = mapper.map( - localMessages, RoomUiModel( + localMessages, RoomUiModel( roles = chatRoles, // FIXME: Why are we fixing isRoom attribute to true here? isBroadcast = chatIsBroadcast, isRoom = true - ) + ) ) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) if (oldMessages.isNotEmpty() && lastSyncDate != null) { @@ -255,15 +257,15 @@ class ChatRoomPresenter @Inject constructor( } private suspend fun loadAndShowMessages( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - clearDataSet: Boolean + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + clearDataSet: Boolean ) { val messages = - retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") { - client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result - } + retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") { + client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result + } messagesRepository.saveAll(messages) //we are saving last sync date of latest synced chat room message @@ -279,11 +281,11 @@ class ChatRoomPresenter @Inject constructor( } view.showMessages( - mapper.map( - messages, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) - ), - clearDataSet + mapper.map( + messages, + RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) + ), + clearDataSet ) } @@ -295,10 +297,10 @@ class ChatRoomPresenter @Inject constructor( client.searchMessages(chatRoomId, searchText).result } view.showSearchedMessages( - mapper.map( - messages, - RoomUiModel(chatRoles, chatIsBroadcast, true) - ), searchText + mapper.map( + messages, + RoomUiModel(chatRoles, chatIsBroadcast, true) + ), searchText ) } catch (ex: Exception) { Timber.e(ex) @@ -322,36 +324,36 @@ class ChatRoomPresenter @Inject constructor( val id = UUID.randomUUID().toString() val username = userHelper.username() val newMessage = Message( - id = id, - roomId = chatRoomId, - message = text, - timestamp = Instant.now().toEpochMilli(), - sender = SimpleUser(null, username, username), - attachments = null, - avatar = currentServer.avatarUrl(username ?: ""), - channels = null, - editedAt = null, - editedBy = null, - groupable = false, - parseUrls = false, - pinned = false, - starred = emptyList(), - mentions = emptyList(), - reactions = null, - senderAlias = null, - type = null, - updatedAt = null, - urls = null, - synced = false, - unread = true + id = id, + roomId = chatRoomId, + message = text, + timestamp = Instant.now().toEpochMilli(), + sender = SimpleUser(null, username, username), + attachments = null, + avatar = currentServer.avatarUrl(username ?: ""), + channels = null, + editedAt = null, + editedBy = null, + groupable = false, + parseUrls = false, + pinned = false, + starred = emptyList(), + mentions = emptyList(), + reactions = null, + senderAlias = null, + type = null, + updatedAt = null, + urls = null, + synced = false, + unread = true ) try { messagesRepository.save(newMessage) view.showNewMessage( - mapper.map( - newMessage, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) - ), false + mapper.map( + newMessage, + RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) + ), false ) client.sendMessage(id, chatRoomId, text) messagesRepository.save(newMessage.copy(synced = true)) @@ -410,14 +412,14 @@ class ChatRoomPresenter @Inject constructor( view.showInvalidFileMessage() } else { val byteArray = - bitmap.getByteArray(mimeType, 100, settings.uploadMaxFileSize()) + bitmap.getByteArray(mimeType, 100, settings.uploadMaxFileSize()) retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { byteArray.inputStream() } @@ -454,11 +456,11 @@ class ChatRoomPresenter @Inject constructor( else -> { retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { uriInteractor.getInputStream(uri) } @@ -495,11 +497,11 @@ class ChatRoomPresenter @Inject constructor( else -> { retryIO("uploadFile($roomId, $fileName, $mimeType") { client.uploadFile( - roomId, - fileName, - mimeType, - msg, - description = fileName + roomId, + fileName, + mimeType, + msg, + description = fileName ) { byteArray.inputStream() } @@ -591,21 +593,23 @@ class ChatRoomPresenter @Inject constructor( // try { val messages = - retryIO(description = "history($chatRoomId, $roomType, $instant)") { - client.history( - chatRoomId, roomType, count = 50, - oldest = instant - ) - } + retryIO(description = "history($chatRoomId, $roomType, $instant)") { + client.history( + chatRoomId, roomType, count = 50, + oldest = instant + ) + } Timber.d("History: $messages") if (messages.result.isNotEmpty()) { - val models = mapper.map(messages.result, RoomUiModel( + val models = mapper.map( + messages.result, RoomUiModel( roles = chatRoles, isBroadcast = chatIsBroadcast, // FIXME: Why are we fixing isRoom attribute to true here? isRoom = true - )) + ) + ) messagesRepository.saveAll(messages.result) //if success - saving last synced time //assume that BE returns ordered messages, the first message is the latest one @@ -673,7 +677,7 @@ class ChatRoomPresenter @Inject constructor( val username = msg.sender?.username ?: "" val mention = if (mentionAuthor && currentUsername != username) "@$username" else "" val room = - if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName + if (roomTypeOf(roomType) is RoomType.DirectMessage) username else roomName val chatRoomType = when (roomTypeOf(roomType)) { is RoomType.DirectMessage -> "direct" is RoomType.PrivateGroup -> "group" @@ -682,14 +686,14 @@ class ChatRoomPresenter @Inject constructor( else -> "custom" } view.showReplyingAction( - username = getDisplayName(msg.sender), - replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", - quotedMessage = mapper.map( - message, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast + username = getDisplayName(msg.sender), + replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", + quotedMessage = mapper.map( + message, RoomUiModel( + roles = chatRoles, + isBroadcast = chatIsBroadcast ) - ).last().preview?.message ?: "" + ).last().preview?.message ?: "" ) } } @@ -792,10 +796,10 @@ class ChatRoomPresenter @Inject constructor( } fun loadActiveMembers( - chatRoomId: String, - chatRoomType: String, - offset: Long = 0, - filterSelfOut: Boolean = false + chatRoomId: String, + chatRoomType: String, + offset: Long = 0, + filterSelfOut: Boolean = false ) { launchUI(strategy) { try { @@ -807,7 +811,7 @@ class ChatRoomPresenter @Inject constructor( val self = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) // Take at most the 100 most recent messages distinguished by user. Can return less. val recentMessages = messagesRepository.getRecentMessages(chatRoomId, 100) - .filterNot { filterSelfOut && it.sender?.username == self } + .filterNot { filterSelfOut && it.sender?.username == self } val activeUsers = mutableListOf() recentMessages.forEach { val sender = it.sender @@ -818,10 +822,10 @@ class ChatRoomPresenter @Inject constructor( val status = if (found != null) found.status else UserStatus.Offline() val searchList = mutableListOf(username, name) activeUsers.add( - PeopleSuggestionUiModel( - avatarUrl, username, username, name, status, - true, searchList - ) + PeopleSuggestionUiModel( + avatarUrl, username, username, name, status, + true, searchList + ) ) } // Filter out from members list the active users. @@ -837,13 +841,13 @@ class ChatRoomPresenter @Inject constructor( val avatarUrl = currentServer.avatarUrl(username) val searchList = mutableListOf(username, name) PeopleSuggestionUiModel( - avatarUrl, - username, - username, - name, - it.status, - true, - searchList + avatarUrl, + username, + username, + name, + it.status, + true, + searchList ) }) @@ -870,8 +874,8 @@ class ChatRoomPresenter @Inject constructor( val searchList = mutableListOf(username, name) it.emails?.forEach { email -> searchList.add(email.address) } PeopleSuggestionUiModel( - currentServer.avatarUrl(username), - username, username, name, it.status, false, searchList + currentServer.avatarUrl(username), + username, username, name, it.status, false, searchList ) }.filterNot { filterSelfOut && self != null && self == it.text }) } @@ -910,10 +914,10 @@ class ChatRoomPresenter @Inject constructor( } fun toChatDetails( - chatRoomId: String, - chatRoomType: String, - isSubscribed: Boolean, - isMenuDisabled: Boolean + chatRoomId: String, + chatRoomType: String, + isSubscribed: Boolean, + isMenuDisabled: Boolean ) { navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isMenuDisabled) } @@ -922,19 +926,19 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { try { val chatRooms = getChatRoomsAsync() - .filterNot { - it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat - } - .map { chatRoom -> - val name = chatRoom.name - val fullName = chatRoom.fullName ?: "" - ChatRoomSuggestionUiModel( - text = name, - name = name, - fullName = fullName, - searchList = listOf(name, fullName) - ) - } + .filterNot { + it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat + } + .map { chatRoom -> + val name = chatRoom.name + val fullName = chatRoom.fullName ?: "" + ChatRoomSuggestionUiModel( + text = name, + name = name, + fullName = fullName, + searchList = listOf(name, fullName) + ) + } view.populateRoomSuggestions(chatRooms) } catch (e: RocketChatException) { Timber.e(e) @@ -948,32 +952,32 @@ class ChatRoomPresenter @Inject constructor( dbManager.chatRoomDao().getSync(roomId)?.let { with(it.chatRoom) { ChatRoom( - id = id, - subscriptionId = subscriptionId, - type = roomTypeOf(type), - unread = unread, - broadcast = broadcast ?: false, - alert = alert, - fullName = fullname, - name = name, - favorite = favorite ?: false, - default = isDefault ?: false, - readonly = readonly, - open = open, - lastMessage = null, - archived = false, - status = null, - user = null, - userMentions = userMentions, - client = client, - announcement = null, - description = null, - groupMentions = groupMentions, - roles = null, - topic = null, - lastSeen = this.lastSeen, - timestamp = timestamp, - updatedAt = updatedAt + id = id, + subscriptionId = subscriptionId, + type = roomTypeOf(type), + unread = unread, + broadcast = broadcast ?: false, + alert = alert, + fullName = fullname, + name = name, + favorite = favorite ?: false, + default = isDefault ?: false, + readonly = readonly, + open = open, + lastMessage = null, + archived = false, + status = null, + user = null, + userMentions = userMentions, + client = client, + announcement = null, + description = null, + groupMentions = groupMentions, + roles = null, + topic = null, + lastSeen = this.lastSeen, + timestamp = timestamp, + updatedAt = updatedAt ) } } @@ -991,32 +995,32 @@ class ChatRoomPresenter @Inject constructor( }.map { with(it.chatRoom) { ChatRoom( - id = id, - subscriptionId = subscriptionId, - type = roomTypeOf(type), - unread = unread, - broadcast = broadcast ?: false, - alert = alert, - fullName = fullname, - name = name ?: "", - favorite = favorite ?: false, - default = isDefault ?: false, - readonly = readonly, - open = open, - lastMessage = null, - archived = false, - status = null, - user = null, - userMentions = userMentions, - client = client, - announcement = null, - description = null, - groupMentions = groupMentions, - roles = null, - topic = null, - lastSeen = this.lastSeen, - timestamp = timestamp, - updatedAt = updatedAt + id = id, + subscriptionId = subscriptionId, + type = roomTypeOf(type), + unread = unread, + broadcast = broadcast ?: false, + alert = alert, + fullName = fullname, + name = name ?: "", + favorite = favorite ?: false, + default = isDefault ?: false, + readonly = readonly, + open = open, + lastMessage = null, + archived = false, + status = null, + user = null, + userMentions = userMentions, + client = client, + announcement = null, + description = null, + groupMentions = groupMentions, + roles = null, + topic = null, + lastSeen = this.lastSeen, + timestamp = timestamp, + updatedAt = updatedAt ) } } @@ -1030,7 +1034,8 @@ class ChatRoomPresenter @Inject constructor( val canPost = permissions.canPostToReadOnlyChannels() dbManager.getRoom(chatRoomId)?.let { val roomUiModel = roomMapper.map(it, true).copy( - writable = canPost) + writable = canPost + ) view.onJoined(roomUiModel = roomUiModel) view.onRoomUpdated(roomUiModel = roomUiModel) } @@ -1047,15 +1052,15 @@ class ChatRoomPresenter @Inject constructor( if (it.isNotEmpty()) { if (it.first().type is RoomType.DirectMessage) { navigator.toDirectMessage( - chatRoomId = it.first().id, - chatRoomType = it.first().type.toString(), - chatRoomLastSeen = it.first().lastSeen ?: -1, - chatRoomName = roomName, - isChatRoomCreator = false, - isChatRoomFavorite = false, - isChatRoomReadOnly = false, - isChatRoomSubscribed = it.first().open, - chatRoomMessage = message + chatRoomId = it.first().id, + chatRoomType = it.first().type.toString(), + chatRoomLastSeen = it.first().lastSeen ?: -1, + chatRoomName = roomName, + isChatRoomCreator = false, + isChatRoomFavorite = false, + isChatRoomReadOnly = false, + isChatRoomSubscribed = it.first().open, + chatRoomMessage = message ) } else { throw IllegalStateException("Not a direct-message") @@ -1157,10 +1162,10 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { val emojiSuggestionUiModels = EmojiRepository.getAll().map { EmojiSuggestionUiModel( - text = it.shortname.replaceFirst(":", ""), - pinned = false, - emoji = it, - searchList = listOf(it.shortname) + text = it.shortname.replaceFirst(":", ""), + pinned = false, + emoji = it, + searchList = listOf(it.shortname) ) } view.populateEmojiSuggestions(emojis = emojiSuggestionUiModels) @@ -1264,9 +1269,9 @@ class ChatRoomPresenter @Inject constructor( private fun updateMessage(streamedMessage: Message) { launchUI(strategy) { val viewModelStreamedMessage = mapper.map( - streamedMessage, RoomUiModel( + streamedMessage, RoomUiModel( roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true - ) + ) ) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id } diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index 2ad8f0eeb2..f8a446ae02 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -101,15 +101,15 @@ import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject fun newInstance( - chatRoomId: String, - chatRoomName: String, - chatRoomType: String, - isReadOnly: Boolean, - chatRoomLastSeen: Long, - isSubscribed: Boolean = true, - isCreator: Boolean = false, - isFavorite: Boolean = false, - chatRoomMessage: String? = null + chatRoomId: String, + chatRoomName: String, + chatRoomType: String, + isReadOnly: Boolean, + chatRoomLastSeen: Long, + isSubscribed: Boolean = true, + isCreator: Boolean = false, + isFavorite: Boolean = false, + chatRoomMessage: String? = null ): Fragment { return ChatRoomFragment().apply { arguments = Bundle(1).apply { @@ -145,7 +145,7 @@ internal const val MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT = 1 internal const val MENU_ACTION_SHOW_DETAILS = 2 class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener, - ChatRoomAdapter.OnActionSelected, Drawable.Callback { + ChatRoomAdapter.OnActionSelected, Drawable.Callback { @Inject lateinit var presenter: ChatRoomPresenter @Inject @@ -182,14 +182,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR // For reveal and unreveal anim. private val hypotenuse by lazy { Math.hypot( - root_layout.width.toDouble(), - root_layout.height.toDouble() + root_layout.width.toDouble(), + root_layout.height.toDouble() ).toFloat() } private val max by lazy { Math.max( - layout_message_attachment_options.width.toDouble(), - layout_message_attachment_options.height.toDouble() + layout_message_attachment_options.width.toDouble(), + layout_message_attachment_options.height.toDouble() ).toFloat() } private val centerX by lazy { recycler_view.right } @@ -208,19 +208,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private var takenPhotoUri: Uri? = null private val layoutChangeListener = - View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom -> - val y = oldBottom - bottom - if (Math.abs(y) > 0 && isAdded) { - // if y is positive the keyboard is up else it's down - recycler_view.post { - if (y > 0 || Math.abs(verticalScrollOffset.get()) >= Math.abs(y)) { - ui { recycler_view.scrollBy(0, y) } - } else { - ui { recycler_view.scrollBy(0, verticalScrollOffset.get()) } - } + View.OnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom -> + val y = oldBottom - bottom + if (Math.abs(y) > 0 && isAdded) { + // if y is positive the keyboard is up else it's down + recycler_view.post { + if (y > 0 || Math.abs(verticalScrollOffset.get()) >= Math.abs(y)) { + ui { recycler_view.scrollBy(0, y) } + } else { + ui { recycler_view.scrollBy(0, verticalScrollOffset.get()) } } } } + } private lateinit var endlessRecyclerViewScrollListener: EndlessRecyclerViewScrollListener @@ -286,13 +286,20 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } } - adapter = ChatRoomAdapter(chatRoomId, chatRoomType, chatRoomName, this, reactionListener = this, navigator = navigator) + adapter = ChatRoomAdapter( + chatRoomId, + chatRoomType, + chatRoomName, + this, + reactionListener = this, + navigator = navigator + ) } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? = container?.inflate(R.layout.fragment_chat_room) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -485,15 +492,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ui { when (usernameList.size) { 1 -> text_typing_status.text = - SpannableStringBuilder() - .bold { append(usernameList[0]) } - .append(getString(R.string.msg_is_typing)) + SpannableStringBuilder() + .bold { append(usernameList[0]) } + .append(getString(R.string.msg_is_typing)) 2 -> text_typing_status.text = - SpannableStringBuilder() - .bold { append(usernameList[0]) } - .append(getString(R.string.msg_and)) - .bold { append(usernameList[1]) } - .append(getString(R.string.msg_are_typing)) + SpannableStringBuilder() + .bold { append(usernameList[0]) } + .append(getString(R.string.msg_and)) + .bold { append(usernameList[1]) } + .append(getString(R.string.msg_are_typing)) else -> text_typing_status.text = getString(R.string.msg_several_users_are_typing) } @@ -684,8 +691,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List) { val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null) val dialog = AlertDialog.Builder(requireContext()) - .setView(layout) - .setCancelable(true) + .setView(layout) + .setCancelable(true) with(layout) { view_flipper.displayedChild = if (isCustom) 1 else 0 @@ -713,7 +720,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } text_view_usernames.text = requireContext().resources.getQuantityString( - R.plurals.msg_reacted_with_, usernames.size, listing, shortname) + R.plurals.msg_reacted_with_, usernames.size, listing, shortname + ) dialog.show() } @@ -794,7 +802,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR recycler_view.layoutManager = linearLayoutManager recycler_view.itemAnimator = DefaultItemAnimator() endlessRecyclerViewScrollListener = object : - EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) { + EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) { override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView) { presenter.loadMessages(chatRoomId, chatRoomType, page * 30L) } @@ -817,12 +825,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR text_room_is_read_only.isVisible = true input_container.isVisible = false text_room_is_read_only.setText( - if (isReadOnly) { - R.string.msg_this_room_is_read_only - } else { - // Not a read-only channel but user has been muted. - R.string.msg_muted_on_this_channel - } + if (isReadOnly) { + R.string.msg_this_room_is_read_only + } else { + // Not a read-only channel but user has been muted. + R.string.msg_muted_on_this_channel + } ) } else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) { input_container.isVisible = false @@ -834,18 +842,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR button_show_attachment_options.alpha = 1f activity?.supportFragmentManager?.registerFragmentLifecycleCallbacks( - object : FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentAttached( - fm: FragmentManager, - f: Fragment, - context: Context - ) { - if (f is MessageActionsBottomSheet) { - dismissEmojiKeyboard() - } + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentAttached( + fm: FragmentManager, + f: Fragment, + context: Context + ) { + if (f is MessageActionsBottomSheet) { + dismissEmojiKeyboard() } - }, - true + } + }, + true ) emojiKeyboardPopup = EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container)) @@ -935,7 +943,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR // Continue only if the File was successfully created photoFile?.also { takenPhotoUri = FileProvider.getUriForFile( - requireContext(), "chat.rocket.android.fileprovider", it + requireContext(), "chat.rocket.android.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, takenPhotoUri) startActivityForResult(takePictureIntent, REQUEST_CODE_FOR_PERFORM_CAMERA) @@ -952,27 +960,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun setupSuggestionsView() { suggestions_view.anchorTo(text_message) - .setMaximumHeight(resources.getDimensionPixelSize(R.dimen.suggestions_box_max_height)) - .addTokenAdapter(PeopleSuggestionsAdapter(context!!)) - .addTokenAdapter(CommandSuggestionsAdapter()) - .addTokenAdapter(RoomSuggestionsAdapter()) - .addTokenAdapter(EmojiSuggestionsAdapter()) - .addSuggestionProviderAction("@") { query -> - if (query.isNotEmpty()) { - presenter.spotlight(query, PEOPLE, true) - } - } - .addSuggestionProviderAction("#") { query -> - if (query.isNotEmpty()) { - presenter.loadChatRoomsSuggestions() - } + .setMaximumHeight(resources.getDimensionPixelSize(R.dimen.suggestions_box_max_height)) + .addTokenAdapter(PeopleSuggestionsAdapter(context!!)) + .addTokenAdapter(CommandSuggestionsAdapter()) + .addTokenAdapter(RoomSuggestionsAdapter()) + .addTokenAdapter(EmojiSuggestionsAdapter()) + .addSuggestionProviderAction("@") { query -> + if (query.isNotEmpty()) { + presenter.spotlight(query, PEOPLE, true) } - .addSuggestionProviderAction("/") { - presenter.loadCommands() - } - .addSuggestionProviderAction(":") { - presenter.loadEmojis() + } + .addSuggestionProviderAction("#") { query -> + if (query.isNotEmpty()) { + presenter.loadChatRoomsSuggestions() } + } + .addSuggestionProviderAction("/") { + presenter.loadCommands() + } + .addSuggestionProviderAction(":") { + presenter.loadEmojis() + } presenter.loadEmojis() presenter.loadCommands() @@ -1001,8 +1009,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun subscribeComposeTextMessage() { text_message.asObservable().let { compositeDisposable.addAll( - subscribeComposeButtons(it), - subscribeComposeTypingStatus(it) + subscribeComposeButtons(it), + subscribeComposeTypingStatus(it) ) } } @@ -1017,8 +1025,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR private fun subscribeComposeTypingStatus(observable: Observable): Disposable { return observable.debounce(300, TimeUnit.MILLISECONDS) - .skip(1) - .subscribe { t -> sendTypingStatus(t) } + .skip(1) + .subscribe { t -> sendTypingStatus(t) } } private fun setupComposeButtons(charSequence: CharSequence) { @@ -1084,10 +1092,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } override fun citeMessage( - roomName: String, - roomType: String, - messageId: String, - mentionAuthor: Boolean + roomName: String, + roomType: String, + messageId: String, + mentionAuthor: Boolean ) { presenter.citeMessage(roomName, roomType, messageId, mentionAuthor) } @@ -1120,15 +1128,15 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ui { val builder = AlertDialog.Builder(it) builder.setTitle(it.getString(R.string.msg_delete_message)) - .setMessage(it.getString(R.string.msg_delete_description)) - .setPositiveButton(it.getString(android.R.string.ok)) { _, _ -> - presenter.deleteMessage( - roomId, - id - ) - } - .setNegativeButton(it.getString(android.R.string.cancel)) { _, _ -> } - .show() + .setMessage(it.getString(R.string.msg_delete_description)) + .setPositiveButton(it.getString(android.R.string.ok)) { _, _ -> + presenter.deleteMessage( + roomId, + id + ) + } + .setNegativeButton(it.getString(android.R.string.cancel)) { _, _ -> } + .show() } } @@ -1149,8 +1157,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } override fun reportMessage(id: String) { - presenter.reportMessage(messageId = id, - description = "This message was reported by a user from the Android app") + presenter.reportMessage( + messageId = id, + description = "This message was reported by a user from the Android app" + ) } fun openEmojiKeyboard() { diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt index 9925f5a307..8cb04ccb9f 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/AttachmentUiModel.kt @@ -6,40 +6,39 @@ import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.actions.Action data class AttachmentUiModel( - override val message: Message, - override val rawData: Attachment, - override val messageId: String, - override var reactions: List, - override var nextDownStreamMessage: BaseUiModel<*>? = null, - override var preview: Message?, - override var isTemporary: Boolean, - override var unread: Boolean?, - override var currentDayMarkerText: String, - override var showDayMarker: Boolean, - override var menuItemsToHide: MutableList = mutableListOf(), - override var permalink: String, - override var isSearchResult: Boolean = false, - override var searchTerm: String = "", - val id: Long, - val title: CharSequence?, - val description: CharSequence?, - val authorName: CharSequence?, - val text: CharSequence?, - val color: Int?, - val imageUrl: String?, - val videoUrl: String?, - val audioUrl: String?, - val titleLink: String?, - val messageLink: String?, - val type: String?, - // TODO - attachments - val timestamp: CharSequence?, - val authorIcon: String?, - val authorLink: String?, - val fields: CharSequence?, - val buttonAlignment: String?, - val actions: List? - + override val message: Message, + override val rawData: Attachment, + override val messageId: String, + override var reactions: List, + override var nextDownStreamMessage: BaseUiModel<*>? = null, + override var preview: Message?, + override var isTemporary: Boolean, + override var unread: Boolean?, + override var currentDayMarkerText: String, + override var showDayMarker: Boolean, + override var menuItemsToHide: MutableList = mutableListOf(), + override var permalink: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String = "", + val id: Long, + val title: CharSequence?, + val description: CharSequence?, + val authorName: CharSequence?, + val text: CharSequence?, + val color: Int?, + val imageUrl: String?, + val videoUrl: String?, + val audioUrl: String?, + val titleLink: String?, + val messageLink: String?, + val type: String?, + // TODO - attachments + val timestamp: CharSequence?, + val authorIcon: String?, + val authorLink: String?, + val fields: CharSequence?, + val buttonAlignment: String?, + val actions: List? ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.ATTACHMENT.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt index befe955030..79953d460e 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt @@ -32,5 +32,5 @@ interface BaseUiModel { internal fun Int.toViewType(): BaseUiModel.ViewType { return BaseUiModel.ViewType.values().firstOrNull { it.viewType == this } - ?: throw InvalidParameterException("Invalid viewType: $this for BaseUiModel.ViewType") + ?: throw InvalidParameterException("Invalid viewType: $this for BaseUiModel.ViewType") } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt index 93ae2a06d2..805aa68b13 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageReplyUiModel.kt @@ -18,7 +18,7 @@ data class MessageReplyUiModel( override var showDayMarker: Boolean, override var permalink: String, override var isSearchResult: Boolean = false, - override var searchTerm: String ="" + override var searchTerm: String = "" ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.MESSAGE_REPLY.viewType diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt index 03fb3677be..1b8f5466bc 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/MessageUiModel.kt @@ -4,27 +4,27 @@ import chat.rocket.android.R import chat.rocket.core.model.Message data class MessageUiModel( - override val message: Message, - override val rawData: Message, - override val messageId: String, - override val avatar: String, - override val time: CharSequence, - override val senderName: CharSequence, - override val content: CharSequence, - override val isPinned: Boolean, - override var currentDayMarkerText: String, - override var showDayMarker: Boolean, - override var reactions: List, - override var nextDownStreamMessage: BaseUiModel<*>? = null, - override var preview: Message? = null, - override var unread: Boolean? = null, - var isFirstUnread: Boolean, - override var isTemporary: Boolean = false, - override var menuItemsToHide: MutableList = mutableListOf(), - override var permalink: String, - val subscriptionId: String, - override var isSearchResult:Boolean = false, - override var searchTerm: String = "" + override val message: Message, + override val rawData: Message, + override val messageId: String, + override val avatar: String, + override val time: CharSequence, + override val senderName: CharSequence, + override val content: CharSequence, + override val isPinned: Boolean, + override var currentDayMarkerText: String, + override var showDayMarker: Boolean, + override var reactions: List, + override var nextDownStreamMessage: BaseUiModel<*>? = null, + override var preview: Message? = null, + override var unread: Boolean? = null, + var isFirstUnread: Boolean, + override var isTemporary: Boolean = false, + override var menuItemsToHide: MutableList = mutableListOf(), + override var permalink: String, + val subscriptionId: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String = "" ) : BaseMessageUiModel { override val viewType: Int diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt index f79d67c728..16357bbcaf 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UrlPreviewUiModel.kt @@ -5,24 +5,24 @@ import chat.rocket.core.model.Message import chat.rocket.core.model.url.Url data class UrlPreviewUiModel( - override val message: Message, - override val rawData: Url, - override val messageId: String, - val title: CharSequence?, - val hostname: String, - val description: CharSequence?, - val thumbUrl: String?, - override var reactions: List, - override var nextDownStreamMessage: BaseUiModel<*>? = null, - override var preview: Message? = null, - override var isTemporary: Boolean = false, - override var unread: Boolean? = null, - override var menuItemsToHide: MutableList = mutableListOf(), - override var currentDayMarkerText: String, - override var showDayMarker: Boolean, - override var permalink: String, - override var isSearchResult: Boolean = false, - override var searchTerm: String = "" + override val message: Message, + override val rawData: Url, + override val messageId: String, + val title: CharSequence?, + val hostname: String, + val description: CharSequence?, + val thumbUrl: String?, + override var reactions: List, + override var nextDownStreamMessage: BaseUiModel<*>? = null, + override var preview: Message? = null, + override var isTemporary: Boolean = false, + override var unread: Boolean? = null, + override var menuItemsToHide: MutableList = mutableListOf(), + override var currentDayMarkerText: String, + override var showDayMarker: Boolean, + override var permalink: String, + override var isSearchResult: Boolean = false, + override var searchTerm: String = "" ) : BaseUiModel { override val viewType: Int get() = BaseUiModel.ViewType.URL_PREVIEW.viewType