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..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 @@ -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 @@ -49,7 +54,27 @@ 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) 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 287b97f467..fc3c532301 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 @@ -153,11 +153,13 @@ class ChatRoomPresenter @Inject constructor( chatIsBroadcast = it.chatRoom.broadcast ?: false val roomUiModel = roomMapper.map(it, true) launchUI(strategy) { - view.onRoomUpdated(roomUiModel = roomUiModel.copy( - broadcast = chatIsBroadcast, - canModerate = canModerate, - writable = roomUiModel.writable || canModerate - )) + view.onRoomUpdated( + roomUiModel = roomUiModel.copy( + broadcast = chatIsBroadcast, + canModerate = canModerate, + writable = roomUiModel.writable || canModerate + ) + ) } } @@ -217,10 +219,10 @@ class ChatRoomPresenter @Inject constructor( 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 - ) + 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) { @@ -298,7 +300,7 @@ class ChatRoomPresenter @Inject constructor( mapper.map( messages, RoomUiModel(chatRoles, chatIsBroadcast, true) - ) + ), searchText ) } catch (ex: Exception) { Timber.e(ex) @@ -601,12 +603,14 @@ class ChatRoomPresenter @Inject constructor( 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 - )) + 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 @@ -687,9 +691,9 @@ class ChatRoomPresenter @Inject constructor( replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", quotedMessage = mapper.map( message, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast - ) + roles = chatRoles, + isBroadcast = chatIsBroadcast + ) ).last().preview?.message ?: "" ) } @@ -1031,7 +1035,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) } @@ -1301,6 +1306,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 758cacefd7..9e38f858c2 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 @@ -287,7 +286,14 @@ 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( @@ -442,8 +448,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 @@ -539,9 +550,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) @@ -711,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() } @@ -1147,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 e191978333..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,37 +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, - 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 5198e6c946..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 @@ -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), @@ -30,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 347f814b24..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 @@ -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..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 @@ -22,7 +22,10 @@ data class MessageUiModel( override var isTemporary: Boolean = false, override var menuItemsToHide: MutableList = mutableListOf(), override var permalink: String, - val subscriptionId: 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..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 @@ -20,7 +20,9 @@ data class UrlPreviewUiModel( 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.URL_PREVIEW.viewType