diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchConfiguration.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchConfiguration.kt index 8655778cb75..3a743af9466 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchConfiguration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchConfiguration.kt @@ -63,6 +63,7 @@ class ContactSearchConfiguration private constructor( val includeV1: Boolean = false, val includeInactive: Boolean = false, val returnAsGroupStories: Boolean = false, + val sortOrder: ContactSearchSortOrder = ContactSearchSortOrder.NATURAL, override val includeHeader: Boolean, override val expandConfig: ExpandConfig? = null ) : Section(SectionKey.GROUPS) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSourceRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSourceRepository.kt index 7e697bd3c28..938377a031a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSourceRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSourceRepository.kt @@ -41,8 +41,19 @@ open class ContactSearchPagedDataSourceRepository( return contactRepository.queryNonGroupContacts(query ?: "", includeSelf) } - open fun getGroupContacts(section: ContactSearchConfiguration.Section.Groups, query: String?): Cursor? { - return SignalDatabase.groups.queryGroupsByTitle(query ?: "", section.includeInactive, !section.includeV1, !section.includeMms).cursor + open fun getGroupContacts( + section: ContactSearchConfiguration.Section.Groups, + query: String? + ): Cursor? { + return SignalDatabase.groups.queryGroups( + GroupDatabase.GroupQuery.Builder() + .withSearchQuery(query) + .withInactiveGroups(section.includeInactive) + .withMmsGroups(section.includeMms) + .withV1Groups(section.includeV1) + .withSortOrder(section.sortOrder) + .build() + ).cursor } open fun getRecents(section: ContactSearchConfiguration.Section.Recents): Cursor? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchSortOrder.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchSortOrder.kt new file mode 100644 index 00000000000..353fbe46573 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchSortOrder.kt @@ -0,0 +1,19 @@ +package org.thoughtcrime.securesms.contacts.paged + +/** + * Different options for sort order of contact search items. + */ +enum class ContactSearchSortOrder { + /** + * The "natural" expected order. This is considered the default ordering. + * For example, Groups would normally be ordered by title from A-Z. + */ + NATURAL, + + /** + * The requested ordering is by recency. This can mean different things for + * different contact types. For example, for Groups, this entry means that + * the results are ordered by latest message date in descending order. + */ + RECENCY +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 2cf4bc31acc..94a27543c66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -24,6 +24,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.EnabledState; +import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder; import org.thoughtcrime.securesms.crypto.SenderKeyUtil; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.BadGroupIdException; @@ -287,6 +288,31 @@ public boolean isUnknownGroup(@NonNull GroupId groupId) { } public Reader queryGroupsByTitle(String inputQuery, boolean includeInactive, boolean excludeV1, boolean excludeMms) { + SqlUtil.Query query = getGroupQueryWhereStatement(inputQuery, includeInactive, excludeV1, excludeMms); + Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query.getWhere(), query.getWhereArgs(), null, null, TITLE + " COLLATE NOCASE ASC"); + + return new Reader(cursor); + } + + public Reader queryGroupsByRecency(@NonNull GroupQuery groupQuery) { + SqlUtil.Query query = getGroupQueryWhereStatement(groupQuery.searchQuery, groupQuery.includeInactive, !groupQuery.includeV1, !groupQuery.includeMms); + String sql = "SELECT * FROM " + TABLE_NAME + + " LEFT JOIN " + ThreadDatabase.TABLE_NAME + " ON " + RECIPIENT_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + + " WHERE " + query.getWhere() + + " ORDER BY " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.DATE + " DESC"; + + return new Reader(databaseHelper.getSignalReadableDatabase().rawQuery(sql, query.getWhereArgs())); + } + + public Reader queryGroups(@NonNull GroupQuery groupQuery) { + if (groupQuery.sortOrder == ContactSearchSortOrder.NATURAL) { + return queryGroupsByTitle(groupQuery.searchQuery, groupQuery.includeInactive, !groupQuery.includeV1, !groupQuery.includeMms); + } else { + return queryGroupsByRecency(groupQuery); + } + } + + private @NonNull SqlUtil.Query getGroupQueryWhereStatement(String inputQuery, boolean includeInactive, boolean excludeV1, boolean excludeMms) { String query; String[] queryArgs; @@ -308,9 +334,7 @@ public Reader queryGroupsByTitle(String inputQuery, boolean includeInactive, boo query += " AND " + MMS + " = 0"; } - Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, queryArgs, null, null, TITLE + " COLLATE NOCASE ASC"); - - return new Reader(cursor); + return new SqlUtil.Query(query, queryArgs); } public @NonNull DistributionId getOrCreateDistributionId(@NonNull GroupId.V2 groupId) { @@ -1445,6 +1469,59 @@ public boolean isInGroup() { } } + public static class GroupQuery { + private final String searchQuery; + private final boolean includeInactive; + private final boolean includeV1; + private final boolean includeMms; + private final ContactSearchSortOrder sortOrder; + + private GroupQuery(@NonNull Builder builder) { + this.searchQuery = builder.searchQuery; + this.includeInactive = builder.includeInactive; + this.includeV1 = builder.includeV1; + this.includeMms = builder.includeMms; + this.sortOrder = builder.sortOrder; + } + + public static class Builder { + private String searchQuery = ""; + private boolean includeInactive = false; + private boolean includeV1 = false; + private boolean includeMms = false; + private ContactSearchSortOrder sortOrder = ContactSearchSortOrder.NATURAL; + + public @NonNull Builder withSearchQuery(@Nullable String query) { + this.searchQuery = TextUtils.isEmpty(query) ? "" : query; + return this; + } + + public @NonNull Builder withInactiveGroups(boolean includeInactive) { + this.includeInactive = includeInactive; + return this; + } + + public @NonNull Builder withV1Groups(boolean includeV1Groups) { + this.includeV1 = includeV1Groups; + return this; + } + + public @NonNull Builder withMmsGroups(boolean includeMmsGroups) { + this.includeMms = includeMmsGroups; + return this; + } + + public @NonNull Builder withSortOrder(@NonNull ContactSearchSortOrder sortOrder) { + this.sortOrder = sortOrder; + return this; + } + + public GroupQuery build() { + return new GroupQuery(this); + } + } + } + public static class LegacyGroupInsertException extends IllegalStateException { public LegacyGroupInsertException(@Nullable GroupId id) { super("Tried to create a new GV1 entry when we already had a migrated GV2! " + id); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt index 039ea2c01af..2f31baa9251 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialog import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.contacts.paged.ContactSearchMediator +import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder import org.thoughtcrime.securesms.sharing.ShareContact import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel @@ -73,7 +74,8 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment( addSection( ContactSearchConfiguration.Section.Groups( includeHeader = false, - returnAsGroupStories = true + returnAsGroupStories = true, + sortOrder = ContactSearchSortOrder.RECENCY ) ) }