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 d59f72e14f..0d58587d80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -206,6 +206,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Set up toolbar buttons binding.profileButton.setOnClickListener { openSettings() } binding.searchViewContainer.setOnClickListener { + globalSearchViewModel.refresh() binding.globalSearchInputLayout.requestFocus() } binding.sessionToolbar.disableClipping() @@ -287,29 +288,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), val hasNames = result.contacts.filter { it.nickname != null || it.name != null } .groupByNotNull { (it.nickname?.firstOrNull() ?: it.name?.firstOrNull())?.uppercase() } .toSortedMap(compareBy { it }) - .flatMap { (key, contacts) -> listOf(GlobalSearchAdapter.Model.SubHeader(key)) + contacts.map(GlobalSearchAdapter.Model::Contact) } + .flatMap { (key, contacts) -> listOf(GlobalSearchAdapter.Model.SubHeader(key)) + contacts.sortedBy { it.nickname ?: it.name }.map(GlobalSearchAdapter.Model::Contact) } val noNames = result.contacts.filter { it.nickname == null && it.name == null } .sortedBy { it.sessionID } .map { GlobalSearchAdapter.Model.Contact(it) } - .takeIf { it.isNotEmpty() } - ?.let { - buildList { - add(GlobalSearchAdapter.Model.Header("Unknown")) - addAll(it) - } - } ?: emptyList() buildList { - add(GlobalSearchAdapter.Model.Header("Contacts")) + add(GlobalSearchAdapter.Model.Header(R.string.contacts)) add(GlobalSearchAdapter.Model.SavedMessages(publicKey)) addAll(hasNames) - addAll(noNames) + noNames.takeIf { it.isNotEmpty() }?.let { + add(GlobalSearchAdapter.Model.Header(R.string.unknown)) + addAll(it) + } } } else { val currentUserPublicKey = publicKey - val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it) } + - result.threads.map { GlobalSearchAdapter.Model.GroupConversation(it) } + val contactAndGroupList = result.contacts.map(GlobalSearchAdapter.Model::Contact) + + result.threads.map(GlobalSearchAdapter.Model::GroupConversation) val contactResults = contactAndGroupList.toMutableList() @@ -323,7 +320,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } if (contactResults.isNotEmpty()) { - contactResults.add(0, GlobalSearchAdapter.Model.Header(R.string.global_search_contacts_groups)) + contactResults.add(0, GlobalSearchAdapter.Model.Header(R.string.conversations)) } val unreadThreadMap = result.messages @@ -393,7 +390,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), Spacer(Modifier.width(12.dp)) OutlineButton( textId = R.string.continue_2, - Modifier.align(Alignment.CenterVertically) + Modifier + .align(Alignment.CenterVertically) .contentDescription(R.string.AccessibilityId_reveal_recovery_phrase_button), onClick = { start() } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt index 6e07b1adfe..380ec0609a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt @@ -3,19 +3,27 @@ package org.thoughtcrime.securesms.home.search import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.coroutines.plus -import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.utilities.SettableFuture -import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.search.SearchRepository import org.thoughtcrime.securesms.search.model.SearchResult import java.util.concurrent.TimeUnit @@ -32,6 +40,8 @@ class GlobalSearchViewModel @Inject constructor( val result: StateFlow = _result + val refreshes = Channel() + private val _queryText: MutableStateFlow = MutableStateFlow("") fun postQuery(charSequence: CharSequence?) { @@ -39,14 +49,22 @@ class GlobalSearchViewModel @Inject constructor( _queryText.value = charSequence } + fun refresh() { + executor.launch { refreshes.send(Unit) } + } + init { - _queryText.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) + _queryText + .reEmit(refreshes) + .buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) .mapLatest { query -> // User input delay in case we get a new query within a few hundred ms this // coroutine will be cancelled and the expensive query will not be run. delay(300) if (query.trim().isEmpty()) { + // searching for 05 as contactDb#getAllContacts was not returning contacts + // without a nickname/name who haven't approved us. GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList()) } else { val settableFuture = SettableFuture() @@ -64,3 +82,9 @@ class GlobalSearchViewModel @Inject constructor( }.launchIn(executor) } } + +/** + * Re-emit whenevr refreshes emits. + * */ +@OptIn(ExperimentalCoroutinesApi::class) +private fun Flow.reEmit(refreshes: Channel) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) }