Skip to content

Commit

Permalink
Add "Group Members" section to ConversationList search results.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal authored and greyson-signal committed Feb 1, 2023
1 parent e84c618 commit 09902e5
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ import org.thoughtcrime.securesms.util.visible
open class ContactSearchAdapter(
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
recipientListener: Listener<ContactSearchData.KnownRecipient>,
storyListener: Listener<ContactSearchData.Story>,
storyContextMenuCallbacks: StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
) : PagingMappingAdapter<ContactSearchKey>() {

init {
registerStoryItems(this, displayCheckBox, storyListener, storyContextMenuCallbacks)
registerKnownRecipientItems(this, displayCheckBox, displaySmsTag, recipientListener)
Expand All @@ -49,7 +50,7 @@ open class ContactSearchAdapter(
fun registerStoryItems(
mappingAdapter: MappingAdapter,
displayCheckBox: Boolean = false,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
storyListener: Listener<ContactSearchData.Story>,
storyContextMenuCallbacks: StoryContextMenuCallbacks? = null
) {
mappingAdapter.registerFactory(
Expand All @@ -62,7 +63,7 @@ open class ContactSearchAdapter(
mappingAdapter: MappingAdapter,
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit
recipientListener: Listener<ContactSearchData.KnownRecipient>
) {
mappingAdapter.registerFactory(
RecipientModel::class.java,
Expand Down Expand Up @@ -97,6 +98,7 @@ open class ContactSearchAdapter(
is ContactSearchData.Message -> MessageModel(it)
is ContactSearchData.Thread -> ThreadModel(it)
is ContactSearchData.Empty -> EmptyModel(it)
is ContactSearchData.GroupWithMembers -> GroupWithMembersModel(it)
}
}
)
Expand Down Expand Up @@ -133,7 +135,7 @@ open class ContactSearchAdapter(
private class StoryViewHolder(
itemView: View,
displayCheckBox: Boolean,
onClick: (View, ContactSearchData.Story, Boolean) -> Unit,
onClick: Listener<ContactSearchData.Story>,
private val storyContextMenuCallbacks: StoryContextMenuCallbacks?
) : BaseRecipientViewHolder<StoryModel, ContactSearchData.Story>(itemView, displayCheckBox, DisplaySmsTag.NEVER, onClick) {
override fun isSelected(model: StoryModel): Boolean = model.isSelected
Expand Down Expand Up @@ -265,7 +267,7 @@ open class ContactSearchAdapter(
itemView: View,
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
onClick: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit
onClick: Listener<ContactSearchData.KnownRecipient>
) : BaseRecipientViewHolder<RecipientModel, ContactSearchData.KnownRecipient>(itemView, displayCheckBox, displaySmsTag, onClick), LetterHeaderDecoration.LetterHeaderItem {

private var headerLetter: String? = null
Expand Down Expand Up @@ -302,7 +304,7 @@ open class ContactSearchAdapter(
itemView: View,
private val displayCheckBox: Boolean,
private val displaySmsTag: DisplaySmsTag,
val onClick: (View, D, Boolean) -> Unit
val onClick: Listener<D>
) : MappingViewHolder<T>(itemView) {

protected val avatar: AvatarImageView = itemView.findViewById(R.id.contact_photo_image)
Expand All @@ -316,7 +318,7 @@ open class ContactSearchAdapter(
override fun bind(model: T) {
checkbox.visible = displayCheckBox
checkbox.isChecked = isSelected(model)
itemView.setOnClickListener { onClick(avatar, getData(model), isSelected(model)) }
itemView.setOnClickListener { onClick.listen(avatar, getData(model), isSelected(model)) }
bindLongPress(model)

if (payload.isNotEmpty()) {
Expand Down Expand Up @@ -420,6 +422,15 @@ open class ContactSearchAdapter(
override fun areContentsTheSame(newItem: EmptyModel): Boolean = newItem.empty == empty
}

/**
* Mapping Model for [ContactSearchData.GroupWithMembers]
*/
class GroupWithMembersModel(val groupWithMembers: ContactSearchData.GroupWithMembers) : MappingModel<GroupWithMembersModel> {
override fun areContentsTheSame(newItem: GroupWithMembersModel): Boolean = newItem.groupWithMembers == groupWithMembers

override fun areItemsTheSame(newItem: GroupWithMembersModel): Boolean = newItem.groupWithMembers.contactSearchKey == groupWithMembers.contactSearchKey
}

/**
* View Holder for section headers
*/
Expand All @@ -439,6 +450,7 @@ open class ContactSearchAdapter(
ContactSearchConfiguration.SectionKey.GROUP_MEMBERS -> R.string.ContactsCursorLoader_group_members
ContactSearchConfiguration.SectionKey.CHATS -> R.string.ContactsCursorLoader__chats
ContactSearchConfiguration.SectionKey.MESSAGES -> R.string.ContactsCursorLoader__messages
ContactSearchConfiguration.SectionKey.GROUPS_WITH_MEMBERS -> R.string.ContactsCursorLoader_group_members
}
)

Expand Down Expand Up @@ -495,4 +507,8 @@ open class ContactSearchAdapter(
IF_NOT_REGISTERED,
NEVER
}

fun interface Listener<D : ContactSearchData> {
fun listen(view: View, data: D, isSelected: Boolean)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ class ContactSearchConfiguration private constructor(
override val expandConfig: ExpandConfig? = null
) : Section(SectionKey.GROUP_MEMBERS)

/**
* Includes a list of groups with members whose search name match the search query.
* This section will only be rendered if there is a non-null, non-empty query present.
*
* Key: [ContactSearchKey.GroupWithMembers]
* Data: [ContactSearchData.GroupWithMembers]
*/
data class GroupsWithMembers(
override val includeHeader: Boolean = true,
override val expandConfig: ExpandConfig? = null
) : Section(SectionKey.GROUPS_WITH_MEMBERS)

data class Chats(
val isUnreadOnly: Boolean = false,
override val includeHeader: Boolean = true,
Expand Down Expand Up @@ -119,6 +131,11 @@ class ContactSearchConfiguration private constructor(
*/
GROUPS,

/**
* Section Key for [Section.GroupsWithMembers]
*/
GROUPS_WITH_MEMBERS,

/**
* Arbitrary row (think new group button, username row, etc)
*/
Expand Down Expand Up @@ -207,6 +224,13 @@ class ContactSearchConfiguration private constructor(
addSection(Section.Arbitrary(setOf(first) + rest.toSet()))
}

fun groupsWithMembers(
includeHeader: Boolean = true,
expandConfig: ExpandConfig? = null
) {
addSection(Section.GroupsWithMembers(includeHeader, expandConfig))
}

fun addSection(section: Section)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Bundle
import androidx.annotation.VisibleForTesting
import org.thoughtcrime.securesms.contacts.HeaderAction
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.search.MessageResult
Expand Down Expand Up @@ -50,6 +51,16 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
val threadRecord: ThreadRecord
) : ContactSearchData(ContactSearchKey.Thread(threadRecord.threadId))

/**
* A row displaying a group which has members that match the given query.
* Rows of this type are only present if the query is non-empty and non-null.
*/
data class GroupWithMembers(
val query: String,
val groupRecord: GroupRecord,
val date: Long
) : ContactSearchData(ContactSearchKey.GroupWithMembers(groupRecord.id))

/**
* A row containing a title for a given section
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.contacts.paged

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sharing.ShareContact

Expand Down Expand Up @@ -48,6 +49,11 @@ sealed class ContactSearchKey {
*/
data class Thread(val threadId: Long) : ContactSearchKey()

/**
* Search key for [ContactSearchData.GroupWithMembers]
*/
data class GroupWithMembers(val groupId: GroupId) : ContactSearchKey()

/**
* Search key for a MessageRecord
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ class ContactSearchMediator(
fun create(
displayCheckBox: Boolean,
displaySmsTag: ContactSearchAdapter.DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
recipientListener: ContactSearchAdapter.Listener<ContactSearchData.KnownRecipient>,
storyListener: ContactSearchAdapter.Listener<ContactSearchData.Story>,
storyContextMenuCallbacks: ContactSearchAdapter.StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
): PagingMappingAdapter<ContactSearchKey>
Expand All @@ -179,8 +179,8 @@ class ContactSearchMediator(
override fun create(
displayCheckBox: Boolean,
displaySmsTag: ContactSearchAdapter.DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
recipientListener: ContactSearchAdapter.Listener<ContactSearchData.KnownRecipient>,
storyListener: ContactSearchAdapter.Listener<ContactSearchData.Story>,
storyContextMenuCallbacks: ContactSearchAdapter.StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
): PagingMappingAdapter<ContactSearchKey> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.contacts.paged

import android.database.Cursor
import org.signal.core.util.requireLong
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchCollection
import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator
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.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
Expand Down Expand Up @@ -112,6 +114,7 @@ class ContactSearchPagedDataSource(
is ContactSearchConfiguration.Section.GroupMembers -> getGroupMembersSearchIterator(query).getCollectionSize(section, query, null)
is ContactSearchConfiguration.Section.Chats -> getThreadData(query, section.isUnreadOnly).getCollectionSize(section, query, null)
is ContactSearchConfiguration.Section.Messages -> getMessageData(query).getCollectionSize(section, query, null)
is ContactSearchConfiguration.Section.GroupsWithMembers -> getGroupsWithMembersIterator(query).getCollectionSize(section, query, null)
}
}

Expand Down Expand Up @@ -146,6 +149,7 @@ class ContactSearchPagedDataSource(
is ContactSearchConfiguration.Section.GroupMembers -> getGroupMembersContactData(section, query, startIndex, endIndex)
is ContactSearchConfiguration.Section.Chats -> getThreadContactData(section, query, startIndex, endIndex)
is ContactSearchConfiguration.Section.Messages -> getMessageContactData(section, query, startIndex, endIndex)
is ContactSearchConfiguration.Section.GroupsWithMembers -> getGroupsWithMembersContactData(section, query, startIndex, endIndex)
}
}

Expand All @@ -168,6 +172,14 @@ class ContactSearchPagedDataSource(
return CursorSearchIterator(contactSearchPagedDataSourceRepository.getStories(query))
}

private fun getGroupsWithMembersIterator(query: String?): ContactSearchIterator<Cursor> {
return if (query.isNullOrEmpty()) {
CursorSearchIterator(null)
} else {
CursorSearchIterator(contactSearchPagedDataSourceRepository.getGroupsWithMembers(query))
}
}

private fun getRecentsSearchIterator(section: ContactSearchConfiguration.Section.Recents, query: String?): ContactSearchIterator<Cursor> {
if (!query.isNullOrEmpty()) {
throw IllegalArgumentException("Searching Recents is not supported")
Expand Down Expand Up @@ -216,6 +228,22 @@ class ContactSearchPagedDataSource(
}
}

private fun getGroupsWithMembersContactData(section: ContactSearchConfiguration.Section.GroupsWithMembers, query: String?, startIndex: Int, endIndex: Int): List<ContactSearchData> {
return getGroupsWithMembersIterator(query).use { records ->
readContactData(
records = records,
recordsPredicate = null,
section = section,
startIndex = startIndex,
endIndex = endIndex,
recordMapper = { cursor ->
val record = GroupTable.Reader(cursor).getCurrent()
ContactSearchData.GroupWithMembers(query!!, record!!, cursor.requireLong(GroupTable.THREAD_DATE))
}
)
}
}

private fun getRecentsContactData(section: ContactSearchConfiguration.Section.Recents, query: String?, startIndex: Int, endIndex: Int): List<ContactSearchData> {
return getRecentsSearchIterator(section, query).use { records ->
readContactData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ open class ContactSearchPagedDataSourceRepository(
return SignalDatabase.distributionLists.getAllListsForContactSelectionUiCursor(query, myStoryContainsQuery(query ?: ""))
}

open fun getGroupsWithMembers(query: String): Cursor {
return SignalDatabase.groups.queryGroupsByMemberName(query)
}

open fun getRecipientFromDistributionListCursor(cursor: Cursor): Recipient {
return Recipient.resolved(RecipientId.from(CursorUtil.requireLong(cursor, DistributionListTables.RECIPIENT_ID)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
expandListener,
(v, t, b) -> {
onConversationClicked(t.getThreadRecord());
return Unit.INSTANCE;
},
(v, m, b) -> {
onMessageClicked(m.getMessageResult());
return Unit.INSTANCE;
},
(v, m, b) -> {
onContactClicked(Recipient.resolved(m.getGroupRecord().getRecipientId()));
},
getViewLifecycleOwner(),
GlideApp.with(this),
Expand Down Expand Up @@ -600,12 +601,12 @@ private ContactSearchConfiguration mapSearchStateToConfiguration(@NonNull Contac
if (TextUtils.isEmpty(state.getQuery())) {
return ContactSearchConfiguration.build(b -> Unit.INSTANCE);
} else {
return ContactSearchConfiguration.build(b -> {
return ContactSearchConfiguration.build(builder -> {
ConversationFilterRequest conversationFilterRequest = state.getConversationFilterRequest();
boolean unreadOnly = conversationFilterRequest != null && conversationFilterRequest.getFilter() == ConversationFilter.UNREAD;

b.setQuery(state.getQuery());
b.addSection(new ContactSearchConfiguration.Section.Chats(
builder.setQuery(state.getQuery());
builder.addSection(new ContactSearchConfiguration.Section.Chats(
unreadOnly,
true,
new ContactSearchConfiguration.ExpandConfig(
Expand All @@ -615,17 +616,22 @@ private ContactSearchConfiguration mapSearchStateToConfiguration(@NonNull Contac
));

if (!unreadOnly) {
builder.addSection(new ContactSearchConfiguration.Section.GroupsWithMembers(
true,
new ContactSearchConfiguration.ExpandConfig(
state.getExpandedSections().contains(ContactSearchConfiguration.SectionKey.GROUPS_WITH_MEMBERS),
(a) -> 5
)
));

// Groups-with-member-section

b.addSection(new ContactSearchConfiguration.Section.Messages(
builder.addSection(new ContactSearchConfiguration.Section.Messages(
true,
null
));

b.setHasEmptyState(true);
builder.setHasEmptyState(true);
} else {
b.arbitrary(
builder.arbitrary(
conversationFilterRequest.getSource() == ConversationFilterSource.DRAG
? ConversationListSearchAdapter.ChatFilterOptions.WITHOUT_TIP.getCode()
: ConversationListSearchAdapter.ChatFilterOptions.WITH_TIP.getCode()
Expand Down

0 comments on commit 09902e5

Please sign in to comment.