mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-06 05:14:50 +01:00
Add more features to group member search.
This commit is contained in:
@@ -280,7 +280,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||
false,
|
||||
new ContactSelectionListAdapter.ArbitraryRepository(),
|
||||
new SearchRepository(requireContext().getString(R.string.note_to_self)),
|
||||
new ContactSearchPagedDataSourceRepository(requireContext()),
|
||||
new ContactSearchPagedDataSourceRepository(requireContext(), requireContext().getString(R.string.note_to_self)),
|
||||
fixedContacts,
|
||||
false
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ import kotlin.Pair;
|
||||
public class ContactRepository {
|
||||
|
||||
private final RecipientTable recipientTable;
|
||||
private final String noteToSelfTitle;
|
||||
private final String selfTitle;
|
||||
|
||||
public static final String ID_COLUMN = "id";
|
||||
public static final String NAME_COLUMN = "name";
|
||||
@@ -101,14 +101,14 @@ public class ContactRepository {
|
||||
}));
|
||||
}};
|
||||
|
||||
public ContactRepository(@NonNull String noteToSelfTitle) {
|
||||
this.noteToSelfTitle = noteToSelfTitle;
|
||||
this.recipientTable = SignalDatabase.recipients();
|
||||
public ContactRepository(@NonNull String selfTitle) {
|
||||
this.selfTitle = selfTitle;
|
||||
this.recipientTable = SignalDatabase.recipients();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public @NonNull Cursor querySignalContacts(@NonNull String query) {
|
||||
return querySignalContacts(new RecipientTable.ContactSearchQuery(query, new RecipientTable.IncludeSelfMode.IncludeWithRemap(noteToSelfTitle), ContactSearchSortOrder.NATURAL));
|
||||
return querySignalContacts(new RecipientTable.ContactSearchQuery(query, new RecipientTable.IncludeSelfMode.IncludeWithRemap(selfTitle), ContactSearchSortOrder.NATURAL));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -123,7 +123,7 @@ public class ContactRepository {
|
||||
public @NonNull Cursor queryGroupMemberContacts(@NonNull String query, @Nullable GroupId groupId) {
|
||||
Cursor cursor;
|
||||
if (groupId != null) {
|
||||
cursor = recipientTable.queryGroupMemberContactsForGroup(groupId, query);
|
||||
cursor = recipientTable.queryGroupMemberContactsForGroup(groupId, query, selfTitle);
|
||||
} else if (TextUtils.isEmpty(query)) {
|
||||
cursor = recipientTable.getGroupMemberContacts();
|
||||
} else {
|
||||
|
||||
+12
-1
@@ -135,7 +135,9 @@ class ContactSearchConfiguration private constructor(
|
||||
override val expandConfig: ExpandConfig? = null,
|
||||
val includeLetterHeaders: Boolean = false,
|
||||
val showGroupsInCommon: Boolean = true,
|
||||
val groupId: GroupId? = null
|
||||
val groupId: GroupId? = null,
|
||||
val showSelfAsYou: Boolean = false,
|
||||
val roleFilter: MemberRole = MemberRole.ALL
|
||||
) : Section(SectionKey.GROUP_MEMBERS)
|
||||
|
||||
/**
|
||||
@@ -299,6 +301,15 @@ class ContactSearchConfiguration private constructor(
|
||||
val maxCountWhenNotExpanded: (ActiveContactCount) -> Int = { 2 }
|
||||
)
|
||||
|
||||
/**
|
||||
* Role-based filter for [Section.GroupMembers].
|
||||
*/
|
||||
enum class MemberRole {
|
||||
ALL,
|
||||
ADMINS,
|
||||
CONTACTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Network transport type for individual recipients.
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,9 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
|
||||
val recipient: Recipient,
|
||||
val shortSummary: Boolean = false,
|
||||
val headerLetter: String? = null,
|
||||
val groupsInCommon: GroupsInCommonSummary = GroupsInCommonSummary(listOf())
|
||||
val groupsInCommon: GroupsInCommonSummary = GroupsInCommonSummary(listOf()),
|
||||
val showSelfAsYou: Boolean = false,
|
||||
val showAdminLabel: Boolean = false
|
||||
) : ContactSearchData(ContactSearchKey.RecipientSearchKey(recipient.id, false))
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -467,7 +466,9 @@ object ContactSearchModels {
|
||||
) : MappingModel<RecipientModel>, FastScrollCharacterProvider {
|
||||
|
||||
override fun getFastScrollCharacter(context: Context): CharSequence {
|
||||
val name = if (knownRecipient.recipient.isSelf) {
|
||||
val name = if (knownRecipient.recipient.isSelf && knownRecipient.showSelfAsYou) {
|
||||
context.getString(R.string.Recipient_you)
|
||||
} else if (knownRecipient.recipient.isSelf) {
|
||||
context.getString(R.string.note_to_self)
|
||||
} else {
|
||||
knownRecipient.recipient.getDisplayName(context)
|
||||
@@ -577,6 +578,16 @@ object ContactSearchModels {
|
||||
override fun isSelected(model: RecipientModel): Boolean = model.isSelected
|
||||
override fun getData(model: RecipientModel): ContactSearchData.KnownRecipient = model.knownRecipient
|
||||
override fun getRecipient(model: RecipientModel): Recipient = model.knownRecipient.recipient
|
||||
override fun showSelfAsYou(model: RecipientModel): Boolean = model.knownRecipient.showSelfAsYou
|
||||
|
||||
override fun bindAvatar(model: RecipientModel) {
|
||||
if (model.knownRecipient.showSelfAsYou) {
|
||||
avatar.setAvatarUsingProfile(getRecipient(model))
|
||||
} else {
|
||||
super.bindAvatar(model)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindNumberField(model: RecipientModel) {
|
||||
val recipient = getRecipient(model)
|
||||
if (model.knownRecipient.sectionKey == ContactSearchConfiguration.SectionKey.GROUP_MEMBERS && displayOptions.displaySecondaryInformation != ContactSearchAdapter.DisplaySecondaryInformation.NEVER) {
|
||||
@@ -609,6 +620,10 @@ object ContactSearchModels {
|
||||
checkbox.isEnabled = !fixedContacts.contains(model.knownRecipient.contactSearchKey)
|
||||
}
|
||||
|
||||
override fun bindLabelField(model: RecipientModel) {
|
||||
adminLabel.visible = model.knownRecipient.showAdminLabel
|
||||
}
|
||||
|
||||
override fun isEnabled(model: RecipientModel): Boolean {
|
||||
return !fixedContacts.contains(model.knownRecipient.contactSearchKey)
|
||||
}
|
||||
@@ -638,6 +653,7 @@ object ContactSearchModels {
|
||||
protected val name: FromTextView = itemView.findViewById(R.id.name)
|
||||
protected val number: TextView = itemView.findViewById(R.id.number)
|
||||
protected val label: TextView = itemView.findViewById(R.id.label)
|
||||
protected val adminLabel: TextView = itemView.findViewById(R.id.admin_label)
|
||||
private val startAudio: View = itemView.findViewById(R.id.start_audio)
|
||||
private val startVideo: View = itemView.findViewById(R.id.start_video)
|
||||
|
||||
@@ -666,7 +682,7 @@ object ContactSearchModels {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
name.setText(recipient, suffix)
|
||||
name.setText(recipient, recipient.getDisplayName(context), suffix, true, showSelfAsYou(model))
|
||||
|
||||
badge.setBadgeFromRecipient(getRecipient(model))
|
||||
|
||||
@@ -682,6 +698,7 @@ object ContactSearchModels {
|
||||
}
|
||||
|
||||
protected open fun isEnabled(model: T): Boolean = true
|
||||
protected open fun showSelfAsYou(model: T): Boolean = false
|
||||
|
||||
protected open fun bindAvatar(model: T) {
|
||||
avatar.setAvatar(getRecipient(model))
|
||||
|
||||
+20
-3
@@ -136,7 +136,7 @@ class ContactSearchPagedDataSource(
|
||||
is ContactSearchConfiguration.Section.Recents -> getRecentsSearchIterator(section, query).getCollectionSizeAndClose(section, query, null)
|
||||
is ContactSearchConfiguration.Section.Stories -> getStoriesSearchIterator(query).getCollectionSizeAndClose(section, query, null)
|
||||
is ContactSearchConfiguration.Section.Arbitrary -> arbitraryRepository?.getSize(section, query) ?: error("Invalid arbitrary section.")
|
||||
is ContactSearchConfiguration.Section.GroupMembers -> getGroupMembersSearchIterator(section, query).getCollectionSizeAndClose(section, query, null)
|
||||
is ContactSearchConfiguration.Section.GroupMembers -> getGroupMembersSearchIterator(section, query).getCollectionSizeAndClose(section, query, filterByRole(section))
|
||||
is ContactSearchConfiguration.Section.Chats -> getThreadData(query, section.isUnreadOnly).getCollectionSizeAndClose(section, query, null)
|
||||
is ContactSearchConfiguration.Section.Messages -> getMessageData(query).getCollectionSizeAndClose(section, query, null)
|
||||
is ContactSearchConfiguration.Section.GroupsWithMembers -> getGroupsWithMembersIterator(query).getCollectionSizeAndClose(section, query, null)
|
||||
@@ -444,12 +444,29 @@ class ContactSearchPagedDataSource(
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterByRole(section: ContactSearchConfiguration.Section.GroupMembers): ((Cursor) -> Boolean)? {
|
||||
if (section.roleFilter == ContactSearchConfiguration.MemberRole.ALL || section.groupId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val groupRecord = contactSearchPagedDataSourceRepository.getGroupRecord(section.groupId) ?: return null
|
||||
return { cursor ->
|
||||
val recipient = contactSearchPagedDataSourceRepository.getRecipientFromSearchCursor(cursor)
|
||||
when (section.roleFilter) {
|
||||
ContactSearchConfiguration.MemberRole.ALL -> true
|
||||
ContactSearchConfiguration.MemberRole.ADMINS -> groupRecord.isAdmin(recipient)
|
||||
ContactSearchConfiguration.MemberRole.CONTACTS -> recipient.isSystemContact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getGroupMembersContactData(section: ContactSearchConfiguration.Section.GroupMembers, query: String?, startIndex: Int, endIndex: Int): List<ContactSearchData> {
|
||||
val groupRecord = section.groupId?.let { contactSearchPagedDataSourceRepository.getGroupRecord(it) }
|
||||
return getGroupMembersSearchIterator(section, query).use { records ->
|
||||
readContactData(
|
||||
records = records,
|
||||
recordsPredicate = null,
|
||||
recordsPredicate = filterByRole(section),
|
||||
section = section,
|
||||
startIndex = startIndex,
|
||||
endIndex = endIndex,
|
||||
@@ -457,7 +474,7 @@ class ContactSearchPagedDataSource(
|
||||
val recipient = contactSearchPagedDataSourceRepository.getRecipientFromSearchCursor(it)
|
||||
val groupsInCommon = if (section.showGroupsInCommon) contactSearchPagedDataSourceRepository.getGroupsInCommon(recipient) else GroupsInCommonSummary(listOf())
|
||||
val headerLetter = if (section.includeLetterHeaders) getHeaderLetterForCurrentRow(it) else null
|
||||
ContactSearchData.KnownRecipient(section.sectionKey, recipient, groupsInCommon = groupsInCommon, headerLetter = headerLetter)
|
||||
ContactSearchData.KnownRecipient(section.sectionKey, recipient, groupsInCommon = groupsInCommon, headerLetter = headerLetter, showSelfAsYou = section.showSelfAsYou, showAdminLabel = groupRecord?.isAdmin(recipient) == true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+17
-2
@@ -6,6 +6,7 @@ import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.core.util.LRUCache
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.ContactRepository
|
||||
import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator
|
||||
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupsInCommonRepository
|
||||
import org.thoughtcrime.securesms.groups.GroupsInCommonSummary
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -28,11 +30,13 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
* having to deal with database access.
|
||||
*/
|
||||
open class ContactSearchPagedDataSourceRepository(
|
||||
context: Context
|
||||
context: Context,
|
||||
selfTitle: String = context.getString(R.string.note_to_self)
|
||||
) {
|
||||
|
||||
private val contactRepository = ContactRepository(context.getString(R.string.note_to_self))
|
||||
private val contactRepository = ContactRepository(selfTitle)
|
||||
private val context = context.applicationContext
|
||||
private val groupRecordCache = LRUCache<GroupId, GroupRecord?>(100)
|
||||
|
||||
open fun getLatestStorySends(activeStoryCutoffDuration: Long): List<StorySend> {
|
||||
return SignalStore.story
|
||||
@@ -142,4 +146,15 @@ open class ContactSearchPagedDataSourceRepository(
|
||||
val myStory = context.getString(R.string.Recipient_my_story)
|
||||
return myStory.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
open fun getGroupRecord(groupId: GroupId): GroupRecord? {
|
||||
if (!groupRecordCache.containsKey(groupId)) {
|
||||
groupRecordCache[groupId] = SignalDatabase.groups.getGroup(groupId).orElse(null)
|
||||
}
|
||||
return groupRecordCache[groupId]
|
||||
}
|
||||
|
||||
open fun clearGroupRecordCache() {
|
||||
groupRecordCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,11 @@ class ContactSearchViewModel(
|
||||
controller.value?.onDataInvalidated()
|
||||
}
|
||||
|
||||
fun refreshGroupData() {
|
||||
contactSearchPagedDataSourceRepository.clearGroupRecordCache()
|
||||
refresh()
|
||||
}
|
||||
|
||||
data class ScrollRequest(val position: Int)
|
||||
|
||||
class Factory(
|
||||
|
||||
+1
-1
@@ -340,7 +340,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
false,
|
||||
new ConversationListSearchAdapter.ChatFilterRepository(),
|
||||
new SearchRepository(requireContext().getString(R.string.note_to_self)),
|
||||
new ContactSearchPagedDataSourceRepository(requireContext()),
|
||||
new ContactSearchPagedDataSourceRepository(requireContext(), requireContext().getString(R.string.note_to_self)),
|
||||
Collections.emptySet(),
|
||||
true
|
||||
)).get(ContactSearchViewModel.class);
|
||||
|
||||
@@ -3546,7 +3546,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
val selfId = Recipient.self().id.toLong()
|
||||
arrayOf(
|
||||
ID,
|
||||
"""CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.noteToSelfTitle}' ELSE $SYSTEM_JOINED_NAME END AS $SYSTEM_JOINED_NAME""",
|
||||
"""CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.title}' ELSE $SYSTEM_JOINED_NAME END AS $SYSTEM_JOINED_NAME""",
|
||||
E164,
|
||||
EMAIL,
|
||||
SYSTEM_PHONE_LABEL,
|
||||
@@ -3556,9 +3556,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
ABOUT_EMOJI,
|
||||
EXTRAS,
|
||||
GROUPS_IN_COMMON,
|
||||
"""CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.noteToSelfTitle}' ELSE COALESCE(NULLIF($PROFILE_JOINED_NAME, ''), NULLIF($PROFILE_GIVEN_NAME, '')) END AS $SEARCH_PROFILE_NAME""",
|
||||
"""CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.title}' ELSE COALESCE(NULLIF($PROFILE_JOINED_NAME, ''), NULLIF($PROFILE_GIVEN_NAME, '')) END AS $SEARCH_PROFILE_NAME""",
|
||||
"""
|
||||
CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.noteToSelfTitle.lowercase()}' ELSE
|
||||
CASE WHEN ${TABLE_NAME}.$ID = $selfId THEN '${includeSelfMode.title.lowercase()}' ELSE
|
||||
LOWER(
|
||||
COALESCE(
|
||||
NULLIF($NICKNAME_JOINED_NAME, ''),
|
||||
@@ -3712,24 +3712,23 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
return readableDatabase.query(TABLE_NAME, searchProjection(IncludeSelfMode.Exclude), selection, args, null, null, orderBy)
|
||||
}
|
||||
|
||||
fun queryGroupMemberContactsForGroup(groupId: GroupId, inputQuery: String): Cursor? {
|
||||
fun queryGroupMemberContactsForGroup(groupId: GroupId, inputQuery: String, selfTitle: String): Cursor? {
|
||||
val orderBy = orderByPreferringAlphaOverNumeric(SORT_NAME) + ", " + E164
|
||||
val queryFilter = if (inputQuery.isNotEmpty()) "AND ($SORT_NAME GLOB ? OR $USERNAME GLOB ?)" else ""
|
||||
|
||||
val selection = """
|
||||
$ID != ? AND
|
||||
$ID IN (SELECT ${GroupTable.MembershipTable.RECIPIENT_ID} FROM ${GroupTable.MembershipTable.TABLE_NAME} WHERE ${GroupTable.MembershipTable.GROUP_ID} = ?)
|
||||
$queryFilter
|
||||
"""
|
||||
|
||||
val args = if (queryFilter.isBlank()) {
|
||||
mutableListOf(Recipient.self().id.serialize(), groupId.toString())
|
||||
mutableListOf(groupId.toString())
|
||||
} else {
|
||||
val query = SqlUtil.buildCaseInsensitiveGlobPattern(inputQuery)
|
||||
mutableListOf(Recipient.self().id.serialize(), groupId.toString(), query, query)
|
||||
mutableListOf(groupId.toString(), query, query)
|
||||
}
|
||||
|
||||
return readableDatabase.query(TABLE_NAME, searchProjection(IncludeSelfMode.Exclude), selection, args.toTypedArray(), null, null, orderBy)
|
||||
return readableDatabase.query(TABLE_NAME, searchProjection(IncludeSelfMode.IncludeWithRemap(selfTitle)), selection, args.toTypedArray(), null, null, orderBy)
|
||||
}
|
||||
|
||||
fun queryAllContacts(inputQuery: String, includeSelfMode: IncludeSelfMode): Cursor? {
|
||||
@@ -4785,7 +4784,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
|
||||
data object Exclude : IncludeSelfMode
|
||||
data object IncludeWithoutRemap : IncludeSelfMode
|
||||
data class IncludeWithRemap(val noteToSelfTitle: String) : IncludeSelfMode
|
||||
data class IncludeWithRemap(val title: String) : IncludeSelfMode
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -4,12 +4,14 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -17,16 +19,23 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.LocalFragmentManager
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.horizontalGutters
|
||||
@@ -51,9 +60,9 @@ import org.thoughtcrime.securesms.search.SearchRepository
|
||||
import org.thoughtcrime.securesms.util.fragments.findListener
|
||||
|
||||
/**
|
||||
* Fragment that shows all members in a group (excluding self)
|
||||
* Fragment that shows all members in a group (including self)
|
||||
*/
|
||||
class MemberSearchFragment : ComposeFragment() {
|
||||
class MemberSearchFragment : ComposeFragment(), RecipientBottomSheetDialogFragment.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -81,16 +90,27 @@ class MemberSearchFragment : ComposeFragment() {
|
||||
performSafetyNumberChecks = false,
|
||||
arbitraryRepository = findListener<SearchConfigurationProvider>()?.getArbitraryRepository(),
|
||||
searchRepository = SearchRepository(requireContext().getString(R.string.Recipient_you)),
|
||||
contactSearchPagedDataSourceRepository = ContactSearchPagedDataSourceRepository(requireContext())
|
||||
contactSearchPagedDataSourceRepository = ContactSearchPagedDataSourceRepository(requireContext(), requireContext().getString(R.string.Recipient_you))
|
||||
)
|
||||
}
|
||||
|
||||
private val memberSearchViewModel: MemberSearchViewModel by viewModels()
|
||||
|
||||
override fun onRecipientBottomSheetDismissed() {
|
||||
contactViewModel.refreshGroupData()
|
||||
}
|
||||
|
||||
override fun onMessageClicked() = Unit
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val memberFilter by memberSearchViewModel.memberFilter.collectAsStateWithLifecycle()
|
||||
CompositionLocalProvider(LocalFragmentManager provides childFragmentManager) {
|
||||
MemberSearchScreen(
|
||||
contactViewModel = contactViewModel,
|
||||
mapStateToConfiguration = this::getConfiguration,
|
||||
memberFilter = memberFilter,
|
||||
onFilterSelected = { memberSearchViewModel.setFilter(it) },
|
||||
mapStateToConfiguration = { state -> getConfiguration(state, memberFilter) },
|
||||
contactSearchCallbacks = remember {
|
||||
SearchCallbacks(
|
||||
fragmentManager = childFragmentManager,
|
||||
@@ -114,7 +134,7 @@ class MemberSearchFragment : ComposeFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(contactSearchState: ContactSearchState): ContactSearchConfiguration {
|
||||
private fun getConfiguration(contactSearchState: ContactSearchState, memberFilter: MemberSearchViewModel.MemberFilter): ContactSearchConfiguration {
|
||||
return findListener<SearchConfigurationProvider>()?.getSearchConfiguration(childFragmentManager, contactSearchState) ?: ContactSearchConfiguration.build {
|
||||
query = contactSearchState.query
|
||||
|
||||
@@ -123,7 +143,13 @@ class MemberSearchFragment : ComposeFragment() {
|
||||
includeHeader = false,
|
||||
includeLetterHeaders = true,
|
||||
groupId = groupId,
|
||||
showGroupsInCommon = false
|
||||
showGroupsInCommon = false,
|
||||
showSelfAsYou = true,
|
||||
roleFilter = when (memberFilter) {
|
||||
MemberSearchViewModel.MemberFilter.ALL -> ContactSearchConfiguration.MemberRole.ALL
|
||||
MemberSearchViewModel.MemberFilter.ADMINS -> ContactSearchConfiguration.MemberRole.ADMINS
|
||||
MemberSearchViewModel.MemberFilter.CONTACTS -> ContactSearchConfiguration.MemberRole.CONTACTS
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -138,10 +164,27 @@ class MemberSearchFragment : ComposeFragment() {
|
||||
@Composable
|
||||
fun MemberSearchScreen(
|
||||
contactViewModel: ContactSearchViewModel,
|
||||
memberFilter: MemberSearchViewModel.MemberFilter,
|
||||
onFilterSelected: (MemberSearchViewModel.MemberFilter) -> Unit,
|
||||
mapStateToConfiguration: (ContactSearchState) -> ContactSearchConfiguration,
|
||||
contactSearchCallbacks: MemberSearchFragment.SearchCallbacks
|
||||
) {
|
||||
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
var showFilterDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showFilterDialog) {
|
||||
val filters = MemberSearchViewModel.MemberFilter.entries
|
||||
|
||||
Dialogs.RadioListDialog(
|
||||
onDismissRequest = { showFilterDialog = false },
|
||||
title = stringResource(R.string.MemberSearchFragment__filter),
|
||||
labels = stringArrayResource(R.array.filter_search_entries),
|
||||
values = filters.map { it.name }.toTypedArray(),
|
||||
selectedIndex = filters.indexOf(memberFilter),
|
||||
onSelected = { index -> onFilterSelected(filters[index]) }
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -153,12 +196,23 @@ fun MemberSearchScreen(
|
||||
contentDescription = stringResource(R.string.DSLSettingsToolbar__navigate_up)
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
val filterActive = memberFilter != MemberSearchViewModel.MemberFilter.ALL
|
||||
IconButton(onClick = { showFilterDialog = true }) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_filter_24),
|
||||
tint = if (filterActive) MaterialTheme.colorScheme.primary else LocalContentColor.current,
|
||||
contentDescription = stringResource(R.string.MemberSearchFragment__filter)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
MemberSearchContent(
|
||||
contactViewModel = contactViewModel,
|
||||
memberFilter = memberFilter,
|
||||
mapStateToConfiguration = mapStateToConfiguration,
|
||||
contactSearchCallbacks = contactSearchCallbacks,
|
||||
modifier = Modifier.padding(it)
|
||||
@@ -169,6 +223,7 @@ fun MemberSearchScreen(
|
||||
@Composable
|
||||
private fun MemberSearchContent(
|
||||
contactViewModel: ContactSearchViewModel,
|
||||
memberFilter: MemberSearchViewModel.MemberFilter,
|
||||
mapStateToConfiguration: (ContactSearchState) -> ContactSearchConfiguration,
|
||||
modifier: Modifier = Modifier,
|
||||
contactSearchCallbacks: MemberSearchFragment.SearchCallbacks
|
||||
@@ -179,34 +234,39 @@ private fun MemberSearchContent(
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
Row(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
val query by contactViewModel.query.collectAsStateWithLifecycle()
|
||||
RecipientSearchBar(
|
||||
hint = stringResource(R.string.MemberSearchFragment__search_members),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp)
|
||||
.focusRequester(focusRequester)
|
||||
.horizontalGutters(),
|
||||
query = query ?: "",
|
||||
onQueryChange = { contactViewModel.setQuery(it) },
|
||||
onSearch = { contactViewModel.setQuery(it) }
|
||||
)
|
||||
val currentMapStateToConfiguration by rememberUpdatedState(mapStateToConfiguration)
|
||||
LaunchedEffect(memberFilter) {
|
||||
contactViewModel.setConfiguration(
|
||||
currentMapStateToConfiguration(contactViewModel.configurationState.value)
|
||||
)
|
||||
}
|
||||
|
||||
ContactSearch(
|
||||
viewModel = contactViewModel,
|
||||
mapStateToConfiguration = mapStateToConfiguration,
|
||||
displayOptions = remember {
|
||||
ContactSearchAdapter.DisplayOptions(
|
||||
displaySecondaryInformation = ContactSearchAdapter.DisplaySecondaryInformation.NEVER
|
||||
)
|
||||
},
|
||||
callbacks = contactSearchCallbacks
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
val query by contactViewModel.query.collectAsStateWithLifecycle()
|
||||
RecipientSearchBar(
|
||||
hint = stringResource(R.string.MemberSearchFragment__search_members),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp)
|
||||
.focusRequester(focusRequester)
|
||||
.horizontalGutters(),
|
||||
query = query ?: "",
|
||||
onQueryChange = { contactViewModel.setQuery(it) },
|
||||
onSearch = { contactViewModel.setQuery(it) }
|
||||
)
|
||||
|
||||
ContactSearch(
|
||||
viewModel = contactViewModel,
|
||||
mapStateToConfiguration = mapStateToConfiguration,
|
||||
displayOptions = remember {
|
||||
ContactSearchAdapter.DisplayOptions(
|
||||
displaySecondaryInformation = ContactSearchAdapter.DisplaySecondaryInformation.NEVER
|
||||
)
|
||||
},
|
||||
callbacks = contactSearchCallbacks,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.groups.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class MemberSearchViewModel : ViewModel() {
|
||||
private val _memberFilter = MutableStateFlow(MemberFilter.ALL)
|
||||
val memberFilter: StateFlow<MemberFilter> = _memberFilter
|
||||
|
||||
fun setFilter(filter: MemberFilter) {
|
||||
_memberFilter.value = filter
|
||||
}
|
||||
|
||||
enum class MemberFilter {
|
||||
ALL,
|
||||
ADMINS,
|
||||
CONTACTS
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user