Add Recency support for contact search ordering.

This commit is contained in:
Alex Hart
2024-03-06 13:35:08 -04:00
parent 2e4ac7ede1
commit 74dc222a54
7 changed files with 53 additions and 18 deletions

View File

@@ -919,7 +919,8 @@ public final class ContactSelectionListFragment extends LoggingFragment {
transportType,
!hideHeader,
null,
!hideLetterHeaders()
!hideLetterHeaders(),
newConversationCallback != null ? ContactSearchSortOrder.RECENCY : ContactSearchSortOrder.NATURAL
));
}

View File

@@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
@@ -108,15 +109,15 @@ public class ContactRepository {
@WorkerThread
public @NonNull Cursor querySignalContacts(@NonNull String query) {
return querySignalContacts(query, true);
return querySignalContacts(new RecipientTable.ContactSearchQuery(query, true, ContactSearchSortOrder.NATURAL));
}
@WorkerThread
public @NonNull Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
Cursor cursor = TextUtils.isEmpty(query) ? recipientTable.getSignalContacts(includeSelf)
: recipientTable.querySignalContacts(query, includeSelf);
public @NonNull Cursor querySignalContacts(@NonNull RecipientTable.ContactSearchQuery contactSearchQuery) {
Cursor cursor = TextUtils.isEmpty(contactSearchQuery.getQuery()) ? recipientTable.getSignalContacts(contactSearchQuery.getIncludeSelf())
: recipientTable.querySignalContacts(contactSearchQuery);
cursor = handleNoteToSelfQuery(query, includeSelf, cursor);
cursor = handleNoteToSelfQuery(contactSearchQuery.getQuery(), contactSearchQuery.getIncludeSelf(), cursor);
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
}

View File

@@ -69,6 +69,8 @@ class ContactSearchConfiguration private constructor(
/**
* 1:1 Recipients with whom the user has started a conversation.
*
* Note that sort order is only respected when returning a query result for signal-only contacts. In all other cases, natural ordering is used.
*
* Key: [ContactSearchKey.RecipientSearchKey]
* Data: [ContactSearchData.KnownRecipient]
* Model: [ContactSearchAdapter.RecipientModel]
@@ -78,7 +80,8 @@ class ContactSearchConfiguration private constructor(
val transportType: TransportType,
override val includeHeader: Boolean,
override val expandConfig: ExpandConfig? = null,
val includeLetterHeaders: Boolean = false
val includeLetterHeaders: Boolean = false,
val pushSearchResultsSortOrder: ContactSearchSortOrder = ContactSearchSortOrder.NATURAL
) : Section(SectionKey.INDIVIDUALS)
/**

View File

@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterat
import org.thoughtcrime.securesms.contacts.paged.collections.CursorSearchIterator
import org.thoughtcrime.securesms.contacts.paged.collections.StoriesSearchCollection
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
@@ -210,7 +211,10 @@ class ContactSearchPagedDataSource(
private fun getNonGroupSearchIterator(section: ContactSearchConfiguration.Section.Individuals, query: String?): ContactSearchIterator<Cursor> {
return when (section.transportType) {
ContactSearchConfiguration.TransportType.PUSH -> CursorSearchIterator(wrapRecipientCursor(contactSearchPagedDataSourceRepository.querySignalContacts(query, section.includeSelf)))
ContactSearchConfiguration.TransportType.PUSH -> {
val searchQuery = RecipientTable.ContactSearchQuery(query ?: "", section.includeSelf, section.pushSearchResultsSortOrder)
CursorSearchIterator(wrapRecipientCursor(contactSearchPagedDataSourceRepository.querySignalContacts(searchQuery)))
}
ContactSearchConfiguration.TransportType.SMS -> CursorSearchIterator(wrapRecipientCursor(contactSearchPagedDataSourceRepository.queryNonSignalContacts(query)))
ContactSearchConfiguration.TransportType.ALL -> CursorSearchIterator(wrapRecipientCursor(contactSearchPagedDataSourceRepository.queryNonGroupContacts(query, section.includeSelf)))
}

View File

@@ -34,8 +34,8 @@ open class ContactSearchPagedDataSourceRepository(
.getLatestActiveStorySendTimestamps(System.currentTimeMillis() - activeStoryCutoffDuration)
}
open fun querySignalContacts(query: String?, includeSelf: Boolean): Cursor? {
return contactRepository.querySignalContacts(query ?: "", includeSelf)
open fun querySignalContacts(contactsSearchQuery: RecipientTable.ContactSearchQuery): Cursor? {
return contactRepository.querySignalContacts(contactsSearchQuery)
}
open fun querySignalContactLetterHeaders(query: String?, includeSelf: Boolean, includePush: Boolean, includeSms: Boolean): Map<RecipientId, String> {

View File

@@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.badges.Badges.toDatabaseBadge
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.color.MaterialColor
import org.thoughtcrime.securesms.color.MaterialColor.UnknownColorException
import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
import org.thoughtcrime.securesms.conversation.colors.ChatColors
@@ -3238,7 +3239,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return getSignalContacts(includeSelf)?.count ?: 0
}
fun getSignalContacts(includeSelf: Boolean, orderBy: String? = null): Cursor? {
private fun getSignalContacts(includeSelf: Boolean, orderBy: String? = null): Cursor? {
val searchSelection = ContactSearchSelection.Builder()
.withRegistered(true)
.withGroups(false)
@@ -3249,20 +3250,39 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
}
fun querySignalContacts(inputQuery: String, includeSelf: Boolean): Cursor? {
val query = SqlUtil.buildCaseInsensitiveGlobPattern(inputQuery)
fun querySignalContacts(contactSearchQuery: ContactSearchQuery): Cursor? {
val query = SqlUtil.buildCaseInsensitiveGlobPattern(contactSearchQuery.query)
val searchSelection = ContactSearchSelection.Builder()
.withRegistered(true)
.withGroups(false)
.excludeId(if (includeSelf) null else Recipient.self().id)
.excludeId(if (contactSearchQuery.includeSelf) null else Recipient.self().id)
.withSearchQuery(query)
.build()
val selection = searchSelection.where
val args = searchSelection.args
val orderBy = "$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $E164"
val orderBy = "${if (contactSearchQuery.contactSearchSortOrder == ContactSearchSortOrder.RECENCY) "${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC, " else ""}$SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $E164"
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
return if (contactSearchQuery.contactSearchSortOrder == ContactSearchSortOrder.RECENCY) {
val ambiguous = listOf(ID)
val projection = SEARCH_PROJECTION.map {
if (it in ambiguous) "$TABLE_NAME.$it" else it
} + "${ThreadTable.TABLE_NAME}.${ThreadTable.DATE}"
//language=roomsql
readableDatabase.query(
"""
SELECT ${projection.joinToString(",")}
FROM $TABLE_NAME
JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = $TABLE_NAME.$ID
WHERE $selection
ORDER BY $orderBy
""".trimIndent(),
args
)
} else {
readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
}
}
fun querySignalContactLetterHeaders(inputQuery: String, includeSelf: Boolean, includePush: Boolean, includeSms: Boolean): Map<RecipientId, String> {
@@ -4337,6 +4357,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
private class GetOrInsertResult(val recipientId: RecipientId, val neededInsert: Boolean)
data class ContactSearchQuery(
val query: String,
val includeSelf: Boolean,
val contactSearchSortOrder: ContactSearchSortOrder = ContactSearchSortOrder.NATURAL
)
@VisibleForTesting
internal class ContactSearchSelection private constructor(val where: String, val args: Array<String>) {