Refresh contacts on open

This commit is contained in:
Andrew 2024-05-08 21:07:42 +09:30
parent cad96001d1
commit 853c165949
2 changed files with 39 additions and 17 deletions

View File

@ -206,6 +206,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
// Set up toolbar buttons // Set up toolbar buttons
binding.profileButton.setOnClickListener { openSettings() } binding.profileButton.setOnClickListener { openSettings() }
binding.searchViewContainer.setOnClickListener { binding.searchViewContainer.setOnClickListener {
globalSearchViewModel.refresh()
binding.globalSearchInputLayout.requestFocus() binding.globalSearchInputLayout.requestFocus()
} }
binding.sessionToolbar.disableClipping() binding.sessionToolbar.disableClipping()
@ -287,29 +288,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
val hasNames = result.contacts.filter { it.nickname != null || it.name != null } val hasNames = result.contacts.filter { it.nickname != null || it.name != null }
.groupByNotNull { (it.nickname?.firstOrNull() ?: it.name?.firstOrNull())?.uppercase() } .groupByNotNull { (it.nickname?.firstOrNull() ?: it.name?.firstOrNull())?.uppercase() }
.toSortedMap(compareBy { it }) .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 } val noNames = result.contacts.filter { it.nickname == null && it.name == null }
.sortedBy { it.sessionID } .sortedBy { it.sessionID }
.map { GlobalSearchAdapter.Model.Contact(it) } .map { GlobalSearchAdapter.Model.Contact(it) }
.takeIf { it.isNotEmpty() }
?.let {
buildList {
add(GlobalSearchAdapter.Model.Header("Unknown"))
addAll(it)
}
} ?: emptyList()
buildList { buildList {
add(GlobalSearchAdapter.Model.Header("Contacts")) add(GlobalSearchAdapter.Model.Header(R.string.contacts))
add(GlobalSearchAdapter.Model.SavedMessages(publicKey)) add(GlobalSearchAdapter.Model.SavedMessages(publicKey))
addAll(hasNames) addAll(hasNames)
addAll(noNames) noNames.takeIf { it.isNotEmpty() }?.let {
add(GlobalSearchAdapter.Model.Header(R.string.unknown))
addAll(it)
}
} }
} else { } else {
val currentUserPublicKey = publicKey val currentUserPublicKey = publicKey
val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it) } + val contactAndGroupList = result.contacts.map(GlobalSearchAdapter.Model::Contact) +
result.threads.map { GlobalSearchAdapter.Model.GroupConversation(it) } result.threads.map(GlobalSearchAdapter.Model::GroupConversation)
val contactResults = contactAndGroupList.toMutableList() val contactResults = contactAndGroupList.toMutableList()
@ -323,7 +320,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
if (contactResults.isNotEmpty()) { 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 val unreadThreadMap = result.messages
@ -393,7 +390,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
OutlineButton( OutlineButton(
textId = R.string.continue_2, textId = R.string.continue_2,
Modifier.align(Alignment.CenterVertically) Modifier
.align(Alignment.CenterVertically)
.contentDescription(R.string.AccessibilityId_reveal_recovery_phrase_button), .contentDescription(R.string.AccessibilityId_reveal_recovery_phrase_button),
onClick = { start<RecoveryPasswordActivity>() } onClick = { start<RecoveryPasswordActivity>() }
) )

View File

@ -3,19 +3,27 @@ package org.thoughtcrime.securesms.home.search
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.buffer 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.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.utilities.SettableFuture import org.session.libsignal.utilities.SettableFuture
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.search.SearchRepository import org.thoughtcrime.securesms.search.SearchRepository
import org.thoughtcrime.securesms.search.model.SearchResult import org.thoughtcrime.securesms.search.model.SearchResult
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -32,6 +40,8 @@ class GlobalSearchViewModel @Inject constructor(
val result: StateFlow<GlobalSearchResult> = _result val result: StateFlow<GlobalSearchResult> = _result
val refreshes = Channel<Unit>()
private val _queryText: MutableStateFlow<CharSequence> = MutableStateFlow("") private val _queryText: MutableStateFlow<CharSequence> = MutableStateFlow("")
fun postQuery(charSequence: CharSequence?) { fun postQuery(charSequence: CharSequence?) {
@ -39,14 +49,22 @@ class GlobalSearchViewModel @Inject constructor(
_queryText.value = charSequence _queryText.value = charSequence
} }
fun refresh() {
executor.launch { refreshes.send(Unit) }
}
init { init {
_queryText.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) _queryText
.reEmit(refreshes)
.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST)
.mapLatest { query -> .mapLatest { query ->
// User input delay in case we get a new query within a few hundred ms this // 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. // coroutine will be cancelled and the expensive query will not be run.
delay(300) delay(300)
if (query.trim().isEmpty()) { 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()) GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList())
} else { } else {
val settableFuture = SettableFuture<SearchResult>() val settableFuture = SettableFuture<SearchResult>()
@ -64,3 +82,9 @@ class GlobalSearchViewModel @Inject constructor(
}.launchIn(executor) }.launchIn(executor)
} }
} }
/**
* Re-emit whenevr refreshes emits.
* */
@OptIn(ExperimentalCoroutinesApi::class)
private fun <T> Flow<T>.reEmit(refreshes: Channel<Unit>) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) }