diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index c17810a646..eb9cca3fb0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -58,6 +59,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter import org.thoughtcrime.securesms.home.search.GlobalSearchInputLayout +import org.thoughtcrime.securesms.home.search.GlobalSearchResult import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.mms.GlideApp @@ -239,67 +241,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } // Get group results and display them launch { - globalSearchViewModel.result.collect { result -> - if (result.query.isEmpty()) { - class NamedValue(val name: String?, val value: T) - - // Unknown is temporarily to be grouped together with numbers title. - // https://optf.atlassian.net/browse/SES-2287 - val numbersTitle = "#" - val unknownTitle = numbersTitle - - listOf( - GlobalSearchAdapter.Model.Header(R.string.contacts), - GlobalSearchAdapter.Model.SavedMessages(publicKey) - ) + result.contacts - // Remove ourself, we're shown above. - .filter { it.accountID != publicKey } - // Get the name that we will display and sort by, and uppercase it to - // help with sorting and we need the char uppercased later. - .map { (it.nickname?.takeIf(String::isNotEmpty) ?: it.name?.takeIf(String::isNotEmpty)) - .let { name -> NamedValue(name?.uppercase(), it) } } - // Digits are all grouped under a #, the rest are grouped by their first character.uppercased() - // If there is no name, they go under Unknown - .groupBy { it.name?.run { first().takeUnless(Char::isDigit)?.toString() ?: numbersTitle } ?: unknownTitle } - // place the # at the end, after all the names starting with alphabetic chars - .toSortedMap(compareBy { - when (it) { - unknownTitle -> Char.MAX_VALUE - numbersTitle -> Char.MAX_VALUE - 1 - else -> it.first() - } - }) - // Flatten the map of char to lists into an actual List that can be displayed. - .flatMap { (key, contacts) -> - listOf( - GlobalSearchAdapter.Model.SubHeader(key) - ) + contacts.sortedBy { it.name ?: it.value.accountID }.map { it.value }.map { GlobalSearchAdapter.Model.Contact(it, it.nickname ?: it.name, it.accountID == publicKey) } + globalSearchViewModel.result.map { result -> + result.query to when { + result.query.isEmpty() -> buildList { + add(GlobalSearchAdapter.Model.Header(R.string.contacts)) + add(GlobalSearchAdapter.Model.SavedMessages(publicKey)) + addAll(result.groupedContacts) + } + else -> buildList { + result.contactAndGroupList.takeUnless { it.isEmpty() }?.let { + add(GlobalSearchAdapter.Model.Header(R.string.contacts)) + addAll(it) + } + result.messageResults.takeUnless { it.isEmpty() }?.let { + add(GlobalSearchAdapter.Model.Header(R.string.global_search_messages)) + addAll(it) } - } else { - val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it, it.nickname ?: it.name, it.accountID == publicKey) } + - result.threads.map(GlobalSearchAdapter.Model::GroupConversation) - - val contactResults = contactAndGroupList.toMutableList() - - if (contactResults.isNotEmpty()) { - contactResults.add(0, GlobalSearchAdapter.Model.Header(R.string.conversations)) } - - val unreadThreadMap = result.messages - .map { it.threadId }.toSet() - .associateWith { mmsSmsDatabase.getUnreadCount(it) } - - val messageResults: MutableList = result.messages - .map { GlobalSearchAdapter.Model.Message(it, unreadThreadMap[it.threadId] ?: 0, it.conversationRecipient.isLocalNumber) } - .toMutableList() - - if (messageResults.isNotEmpty()) { - messageResults.add(0, GlobalSearchAdapter.Model.Header(R.string.global_search_messages)) - } - - contactResults + messageResults - }.let { globalSearchAdapter.setNewData(result.query, it) } - } + } + }.collectLatest(globalSearchAdapter::setNewData) } } EventBus.getDefault().register(this@HomeActivity) @@ -317,6 +277,54 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } } + private val GlobalSearchResult.groupedContacts: List get() { + class NamedValue(val name: String?, val value: T) + + // Unknown is temporarily to be grouped together with numbers title. + // https://optf.atlassian.net/browse/SES-2287 + val numbersTitle = "#" + val unknownTitle = numbersTitle + + return contacts + // Remove ourself, we're shown above. + .filter { it.accountID != publicKey } + // Get the name that we will display and sort by, and uppercase it to + // help with sorting and we need the char uppercased later. + .map { (it.nickname?.takeIf(String::isNotEmpty) ?: it.name?.takeIf(String::isNotEmpty)) + .let { name -> NamedValue(name?.uppercase(), it) } } + // Digits are all grouped under a #, the rest are grouped by their first character.uppercased() + // If there is no name, they go under Unknown + .groupBy { it.name?.run { first().takeUnless(Char::isDigit)?.toString() ?: numbersTitle } ?: unknownTitle } + // place the # at the end, after all the names starting with alphabetic chars + .toSortedMap(compareBy { + when (it) { + unknownTitle -> Char.MAX_VALUE + numbersTitle -> Char.MAX_VALUE - 1 + else -> it.first() + } + }) + // Flatten the map of char to lists into an actual List that can be displayed. + .flatMap { (key, contacts) -> + listOf( + GlobalSearchAdapter.Model.SubHeader(key) + ) + contacts.sortedBy { it.name ?: it.value.accountID }.map { it.value }.map { GlobalSearchAdapter.Model.Contact(it, it.nickname ?: it.name, it.accountID == publicKey) } + } + } + + private val GlobalSearchResult.contactAndGroupList: List get() = + contacts.map { GlobalSearchAdapter.Model.Contact(it, it.nickname ?: it.name, it.accountID == publicKey) } + + threads.map(GlobalSearchAdapter.Model::GroupConversation) + + private val GlobalSearchResult.messageResults: List get() { + val unreadThreadMap = messages + .map { it.threadId }.toSet() + .associateWith { mmsSmsDatabase.getUnreadCount(it) } + + return messages.map { + GlobalSearchAdapter.Model.Message(it, unreadThreadMap[it.threadId] ?: 0, it.conversationRecipient.isLocalNumber) + } + } + override fun onInputFocusChanged(hasFocus: Boolean) { setSearchShown(hasFocus || binding.globalSearchInputLayout.query.value.isNotEmpty()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index 27f858c92b..71c2c62506 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -28,6 +28,8 @@ class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerVie private var data: List = listOf() private var query: String? = null + fun setNewData(data: Pair>) = setNewData(data.first, data.second) + fun setNewData(query: String, newData: List) { val diffResult = DiffUtil.calculateDiff(GlobalSearchDiff(this.query, query, data, newData)) this.query = query