package org.thoughtcrime.securesms.home.search import android.graphics.Typeface import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.StyleSpan import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.LegacyGroupConversation import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Header import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMessages import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.SearchUtil import java.util.Locale import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Contact as ContactModel class GlobalSearchDiff( private val oldQuery: String?, private val newQuery: String?, private val oldData: List, private val newData: List ) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldData.size override fun getNewListSize(): Int = newData.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldData[oldItemPosition] == newData[newItemPosition] override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldQuery == newQuery && oldData[oldItemPosition] == newData[newItemPosition] override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? = if (oldQuery != newQuery) newQuery else null } private val BoldStyleFactory = { StyleSpan(Typeface.BOLD) } fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) { when (model) { is ContactModel -> { binding.searchResultTitle.text = getHighlight( query, model.contact.getSearchName() ) } is Message -> { val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { // group chat, bind val text = "${model.messageResult.messageRecipient.getSearchName()}: " textSpannable.append(text) } textSpannable.append(getHighlight( query, model.messageResult.bodySnippet )) binding.searchResultSubtitle.text = textSpannable binding.searchResultSubtitle.isVisible = true binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() } is LegacyGroupConversation -> { binding.searchResultTitle.text = getHighlight( query, model.groupRecord.title ) val membersString = model.groupRecord.members.joinToString { address -> val recipient = Recipient.from(binding.root.context, address, false) recipient.name ?: "${address.serialize().take(4)}...${address.serialize().takeLast(4)}" } binding.searchResultSubtitle.text = getHighlight(query, membersString) } is Header, // do nothing for header is SavedMessages -> Unit // do nothing for saved messages (displays note to self) } } private fun getHighlight(query: String?, toSearch: String): Spannable? { return SearchUtil.getHighlightedSpan(Locale.getDefault(), BoldStyleFactory, toSearch, query) } fun ContentView.bindModel(query: String?, model: LegacyGroupConversation) { binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultSubtitle.isVisible = model.groupRecord.isLegacyClosedGroup binding.searchResultTimestamp.isVisible = false val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false) binding.searchResultProfilePicture.update(threadRecipient) val nameString = model.groupRecord.title binding.searchResultTitle.text = getHighlight(query, nameString) val groupRecipients = model.groupRecord.members.map { Recipient.from(binding.root.context, it, false) } val membersString = groupRecipients.joinToString { val address = it.address.serialize() it.name ?: "${address.take(4)}...${address.takeLast(4)}" } if (model.groupRecord.isLegacyClosedGroup) { binding.searchResultSubtitle.text = getHighlight(query, membersString) } } fun ContentView.bindModel(query: String?, model: ContactModel) { binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultSubtitle.isVisible = false binding.searchResultTimestamp.isVisible = false binding.searchResultSubtitle.text = null val recipient = Recipient.from(binding.root.context, Address.fromSerialized(model.contact.sessionID), false) binding.searchResultProfilePicture.update(recipient) val nameString = model.contact.getSearchName() binding.searchResultTitle.text = getHighlight(query, nameString) } fun ContentView.bindModel(model: SavedMessages) { binding.searchResultSubtitle.isVisible = false binding.searchResultTimestamp.isVisible = false binding.searchResultTitle.setText(R.string.note_to_self) binding.searchResultProfilePicture.isVisible = false binding.searchResultSavedMessages.isVisible = true } fun ContentView.bindModel(query: String?, model: Message) { binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultTimestamp.isVisible = true binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient) val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { // group chat, bind val text = "${model.messageResult.messageRecipient.getSearchName()}: " textSpannable.append(text) } textSpannable.append(getHighlight( query, model.messageResult.bodySnippet )) binding.searchResultSubtitle.text = textSpannable binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() binding.searchResultSubtitle.isVisible = true } fun Recipient.getSearchName(): String = name ?: address.serialize().let { address -> "${address.take(4)}...${address.takeLast(4)}" } fun Contact.getSearchName(): String = if (nickname.isNullOrEmpty()) name ?: "${sessionID.take(4)}...${sessionID.takeLast(4)}" else "${name ?: "${sessionID.take(4)}...${sessionID.takeLast(4)}"} ($nickname)"