mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-07 22:06:06 +01:00
Fix contact search list flickering on query change.
This commit is contained in:
committed by
Cody Henthorne
parent
9e3ee16e65
commit
4cdd1f70ac
@@ -35,6 +35,7 @@ public class ContactRepository {
|
||||
|
||||
public static final String ID_COLUMN = "id";
|
||||
public static final String NAME_COLUMN = "name";
|
||||
public static final String SORT_NAME_COLUMN = "sort_name";
|
||||
static final String NUMBER_COLUMN = "number";
|
||||
static final String NUMBER_TYPE_COLUMN = "number_type";
|
||||
static final String LABEL_COLUMN = "label";
|
||||
@@ -55,6 +56,11 @@ public class ContactRepository {
|
||||
return Util.getFirstNonEmpty(system, profile);
|
||||
}));
|
||||
|
||||
// The key the results are actually ordered by (nickname/system/profile/username, lowercased). Letter
|
||||
// headers must derive from this rather than NAME_COLUMN, which omits nickname/username and can begin
|
||||
// with a different letter than the row's sort position.
|
||||
add(new Pair<>(SORT_NAME_COLUMN, cursor -> CursorUtil.requireString(cursor, RecipientTable.SORT_NAME)));
|
||||
|
||||
add(new Pair<>(NUMBER_COLUMN, cursor -> {
|
||||
String phone = CursorUtil.requireString(cursor, RecipientTable.E164);
|
||||
String email = CursorUtil.requireString(cursor, RecipientTable.EMAIL);
|
||||
|
||||
+29
-15
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.database.model.ThreadWithRecipient
|
||||
import org.thoughtcrime.securesms.keyvalue.StorySend
|
||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.search.MessageResult
|
||||
import org.thoughtcrime.securesms.search.MessageSearchResult
|
||||
import org.thoughtcrime.securesms.search.SearchRepository
|
||||
@@ -240,13 +239,33 @@ class ContactSearchPagedDataSource(
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun getNonGroupHeaderLetterMap(section: ContactSearchConfiguration.Section.Individuals, query: String?): Map<RecipientId, String> {
|
||||
return contactSearchPagedDataSourceRepository.querySignalContactLetterHeaders(
|
||||
query = query,
|
||||
includeSelfMode = section.includeSelfMode,
|
||||
includePush = true,
|
||||
includeSms = false
|
||||
)
|
||||
/**
|
||||
* Returns the letter header to display above the recipient at the cursor's current row, or null if
|
||||
* none should be shown. A header is shown only when this row begins a new letter group, determined by
|
||||
* comparing its letter to the immediately preceding row in display order. Peeking that single adjacent
|
||||
* row means a letter group split across pages still yields exactly one header, anchored to the first
|
||||
* row of the group, without re-scanning the whole contact set.
|
||||
*
|
||||
* The cursor is restored to its original position before returning so iteration is unaffected.
|
||||
*/
|
||||
private fun getHeaderLetterForCurrentRow(cursor: Cursor): String? {
|
||||
val position = cursor.position
|
||||
val currentLetter = letterForCurrentRow(cursor) ?: return null
|
||||
|
||||
if (position <= 0) {
|
||||
return currentLetter
|
||||
}
|
||||
|
||||
cursor.moveToPosition(position - 1)
|
||||
val previousLetter = letterForCurrentRow(cursor)
|
||||
cursor.moveToPosition(position)
|
||||
|
||||
return if (previousLetter != currentLetter) currentLetter else null
|
||||
}
|
||||
|
||||
private fun letterForCurrentRow(cursor: Cursor): String? {
|
||||
val sortName = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.SORT_NAME_COLUMN))
|
||||
return sortName?.takeIf { it.isNotEmpty() }?.first()?.uppercaseChar()?.toString()
|
||||
}
|
||||
|
||||
private fun getStoriesSearchIterator(query: String?): ContactSearchIterator<Cursor> {
|
||||
@@ -379,12 +398,6 @@ class ContactSearchPagedDataSource(
|
||||
}
|
||||
|
||||
private fun getNonGroupContactsData(section: ContactSearchConfiguration.Section.Individuals, query: String?, startIndex: Int, endIndex: Int): List<ContactSearchData> {
|
||||
val headerMap: Map<RecipientId, String> = if (section.includeLetterHeaders) {
|
||||
getNonGroupHeaderLetterMap(section, query)
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
return getNonGroupSearchIterator(section, query).use { records ->
|
||||
readContactData(
|
||||
records = records,
|
||||
@@ -394,7 +407,8 @@ class ContactSearchPagedDataSource(
|
||||
endIndex = endIndex,
|
||||
recordMapper = {
|
||||
val recipient = contactSearchPagedDataSourceRepository.getRecipientFromSearchCursor(it)
|
||||
ContactSearchData.KnownRecipient(section.sectionKey, recipient, headerLetter = headerMap[recipient.id])
|
||||
val headerLetter = if (section.includeLetterHeaders) getHeaderLetterForCurrentRow(it) else null
|
||||
ContactSearchData.KnownRecipient(section.sectionKey, recipient, headerLetter = headerLetter)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
-4
@@ -43,10 +43,6 @@ open class ContactSearchPagedDataSourceRepository(
|
||||
return contactRepository.querySignalContacts(contactsSearchQuery)
|
||||
}
|
||||
|
||||
open fun querySignalContactLetterHeaders(query: String?, includeSelfMode: RecipientTable.IncludeSelfMode, includePush: Boolean, includeSms: Boolean): Map<RecipientId, String> {
|
||||
return SignalDatabase.recipients.querySignalContactLetterHeaders(query ?: "", includeSelfMode, includePush, includeSms)
|
||||
}
|
||||
|
||||
open fun queryGroupMemberContacts(query: String?): Cursor? {
|
||||
return contactRepository.queryGroupMemberContacts(query ?: "")
|
||||
}
|
||||
|
||||
+11
-10
@@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
@@ -98,7 +97,7 @@ class ContactSearchViewModel(
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
rawQuery.drop(1).debounce(300).collect { query ->
|
||||
rawQuery.drop(1).collect { query ->
|
||||
savedStateHandle[QUERY] = query
|
||||
internalConfigurationState.update { it.copy(query = query) }
|
||||
}
|
||||
@@ -148,15 +147,17 @@ class ContactSearchViewModel(
|
||||
}
|
||||
|
||||
suspend fun setConfiguration(contactSearchConfiguration: ContactSearchConfiguration) {
|
||||
val pagedDataSource = ContactSearchPagedDataSource(
|
||||
contactSearchConfiguration,
|
||||
arbitraryRepository = arbitraryRepository,
|
||||
searchRepository = searchRepository,
|
||||
contactSearchPagedDataSourceRepository = contactSearchPagedDataSourceRepository
|
||||
)
|
||||
val size = withContext(Dispatchers.IO) { pagedDataSource.size() }
|
||||
val (pagedDataSource, size) = withContext(Dispatchers.IO) {
|
||||
val source = ContactSearchPagedDataSource(
|
||||
contactSearchConfiguration,
|
||||
arbitraryRepository = arbitraryRepository,
|
||||
searchRepository = searchRepository,
|
||||
contactSearchPagedDataSourceRepository = contactSearchPagedDataSourceRepository
|
||||
)
|
||||
source to source.size()
|
||||
}
|
||||
internalTotalCount.value = size
|
||||
pagedData.value = PagedData.createForStateFlow(pagedDataSource, pagingConfig)
|
||||
pagedData.value = PagedData.createForStateFlow(pagedDataSource, pagingConfig, data.value)
|
||||
}
|
||||
|
||||
fun setQuery(query: String?) {
|
||||
|
||||
Reference in New Issue
Block a user