change(members): use SearchField and update strings
This commit is contained in:
parent
b243394b67
commit
1b0beda620
9 changed files with 113 additions and 226 deletions
|
|
@ -35,8 +35,8 @@
|
||||||
<string name="screen_room_change_role_unsaved_changes_title">"Save changes?"</string>
|
<string name="screen_room_change_role_unsaved_changes_title">"Save changes?"</string>
|
||||||
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
|
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
|
||||||
<plurals name="screen_room_member_list_header_title">
|
<plurals name="screen_room_member_list_header_title">
|
||||||
<item quantity="one">"%1$d person"</item>
|
<item quantity="one">"%1$d Person"</item>
|
||||||
<item quantity="other">"%1$d people"</item>
|
<item quantity="other">"%1$d People"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"Ban user"</string>
|
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"Ban user"</string>
|
||||||
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"Only remove member"</string>
|
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"Only remove member"</string>
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
<string name="screen_room_member_list_manage_member_unban_title">"Unban user"</string>
|
<string name="screen_room_member_list_manage_member_unban_title">"Unban user"</string>
|
||||||
<string name="screen_room_member_list_mode_banned">"Banned"</string>
|
<string name="screen_room_member_list_mode_banned">"Banned"</string>
|
||||||
<string name="screen_room_member_list_mode_members">"Members"</string>
|
<string name="screen_room_member_list_mode_members">"Members"</string>
|
||||||
<string name="screen_room_member_list_pending_header_title">"Pending"</string>
|
<string name="screen_room_member_list_pending_header_title">"%1$d Invited"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
||||||
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
||||||
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.members
|
|
||||||
|
|
||||||
import dev.zacsweers.metro.Inject
|
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlin.collections.filter
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
class RoomMemberListDataSource(
|
|
||||||
private val room: BaseRoom,
|
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
|
||||||
) {
|
|
||||||
suspend fun search(query: String, selectedSection: SelectedSection): List<RoomMember> = withContext(coroutineDispatchers.io) {
|
|
||||||
val roomMembersState = room.membersStateFlow.value
|
|
||||||
val displayableMembers = roomMembersState.roomMembers()
|
|
||||||
.orEmpty()
|
|
||||||
.filter {
|
|
||||||
when(selectedSection){
|
|
||||||
SelectedSection.MEMBERS -> it.membership.isActive()
|
|
||||||
SelectedSection.BANNED -> it.membership == RoomMembershipState.BAN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val filteredMembers = if (query.isBlank()) {
|
|
||||||
displayableMembers
|
|
||||||
} else {
|
|
||||||
displayableMembers.filter { member ->
|
|
||||||
member.userId.value.contains(query, ignoreCase = true) ||
|
|
||||||
member.displayName?.contains(query, ignoreCase = true).orFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filteredMembers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,6 +13,5 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
sealed interface RoomMemberListEvents {
|
sealed interface RoomMemberListEvents {
|
||||||
data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents
|
data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents
|
||||||
data class UpdateSearchQuery(val query: String) : RoomMemberListEvents
|
data class UpdateSearchQuery(val query: String) : RoomMemberListEvents
|
||||||
data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents
|
|
||||||
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents
|
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.architecture.map
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
@ -41,7 +42,6 @@ import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -63,8 +63,6 @@ class RoomMemberListPresenter(
|
||||||
var searchResults by remember {
|
var searchResults by remember {
|
||||||
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
||||||
}
|
}
|
||||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val membersState by room.membersStateFlow.collectAsState()
|
val membersState by room.membersStateFlow.collectAsState()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
||||||
|
|
@ -78,8 +76,9 @@ class RoomMemberListPresenter(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading())}
|
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||||
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS)}
|
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) }
|
||||||
|
var filteredRoomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||||
|
|
||||||
// Update the room members when the screen is loaded
|
// Update the room members when the screen is loaded
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -98,7 +97,7 @@ class RoomMemberListPresenter(
|
||||||
}
|
}
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
|
val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
|
||||||
val info = room.roomInfoFlow.first()
|
val info = room.info()
|
||||||
if (members.getOrDefault(RoomMembershipState.JOIN, emptyList()).size < info.joinedMembersCount / 2) {
|
if (members.getOrDefault(RoomMembershipState.JOIN, emptyList()).size < info.joinedMembersCount / 2) {
|
||||||
// Don't display initial room member list if we have less than half of the joined members:
|
// Don't display initial room member list if we have less than half of the joined members:
|
||||||
// This result will come from the timeline loading membership events and it'll be wrong.
|
// This result will come from the timeline loading membership events and it'll be wrong.
|
||||||
|
|
@ -125,43 +124,16 @@ class RoomMemberListPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(membersState, searchQuery, isSearchActive) {
|
LaunchedEffect(searchQuery, roomMembers) {
|
||||||
withContext(coroutineDispatchers.io) {
|
filteredRoomMembers = roomMembers.map { members ->
|
||||||
searchResults = if (searchQuery.isEmpty() || !isSearchActive) {
|
withContext(coroutineDispatchers.io) {
|
||||||
SearchBarResultState.Initial()
|
members.filter(searchQuery)
|
||||||
} else {
|
|
||||||
val results = roomMemberListDataSource.search(searchQuery, selectedSection).groupBy { it.membership }
|
|
||||||
if (results.isEmpty()) {
|
|
||||||
SearchBarResultState.NoResultsFound()
|
|
||||||
} else {
|
|
||||||
val result = RoomMembers(
|
|
||||||
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList())
|
|
||||||
.map { it.withIdentityState(roomMemberIdentityStates) }
|
|
||||||
.toImmutableList(),
|
|
||||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
|
||||||
.sortedWith(powerLevelRoomMemberComparator)
|
|
||||||
.map { it.withIdentityState(roomMemberIdentityStates) }
|
|
||||||
.toImmutableList(),
|
|
||||||
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList())
|
|
||||||
.sortedBy { it.userId.value }
|
|
||||||
.map { it.withIdentityState(roomMemberIdentityStates) }
|
|
||||||
.toImmutableList(),
|
|
||||||
)
|
|
||||||
SearchBarResultState.Results(
|
|
||||||
if (membersState is RoomMembersState.Pending) {
|
|
||||||
AsyncData.Loading(result)
|
|
||||||
} else {
|
|
||||||
AsyncData.Success(result)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvent(event: RoomMemberListEvents) {
|
fun handleEvent(event: RoomMemberListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
|
||||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||||
is RoomMemberListEvents.RoomMemberSelected ->
|
is RoomMemberListEvents.RoomMemberSelected ->
|
||||||
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
|
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
|
||||||
|
|
@ -176,10 +148,8 @@ class RoomMemberListPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomMemberListState(
|
return RoomMemberListState(
|
||||||
roomMembers = roomMembers,
|
roomMembers = filteredRoomMembers,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
searchResults = searchResults,
|
|
||||||
isSearchActive = isSearchActive,
|
|
||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
moderationState = roomModerationState,
|
moderationState = roomModerationState,
|
||||||
selectedSection = selectedSection,
|
selectedSection = selectedSection,
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,15 @@ package io.element.android.features.roomdetails.impl.members
|
||||||
|
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
data class RoomMemberListState(
|
data class RoomMemberListState(
|
||||||
val roomMembers: AsyncData<RoomMembers>,
|
val roomMembers: AsyncData<RoomMembers>,
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val searchResults: SearchBarResultState<AsyncData<RoomMembers>>,
|
|
||||||
val isSearchActive: Boolean,
|
|
||||||
val canInvite: Boolean,
|
val canInvite: Boolean,
|
||||||
val selectedSection: SelectedSection,
|
val selectedSection: SelectedSection,
|
||||||
val moderationState: RoomMemberModerationState,
|
val moderationState: RoomMemberModerationState,
|
||||||
|
|
@ -35,7 +34,22 @@ data class RoomMembers(
|
||||||
val invited: ImmutableList<RoomMemberWithIdentityState>,
|
val invited: ImmutableList<RoomMemberWithIdentityState>,
|
||||||
val joined: ImmutableList<RoomMemberWithIdentityState>,
|
val joined: ImmutableList<RoomMemberWithIdentityState>,
|
||||||
val banned: ImmutableList<RoomMemberWithIdentityState>,
|
val banned: ImmutableList<RoomMemberWithIdentityState>,
|
||||||
)
|
){
|
||||||
|
fun filter(query: String): RoomMembers {
|
||||||
|
if (query.isBlank()) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
val filterPredicate = { member: RoomMemberWithIdentityState ->
|
||||||
|
member.roomMember.userId.value.contains(query, ignoreCase = true) ||
|
||||||
|
member.roomMember.displayName?.contains(query, ignoreCase = true).orFalse()
|
||||||
|
}
|
||||||
|
return RoomMembers(
|
||||||
|
invited = invited.filter(filterPredicate).toImmutableList(),
|
||||||
|
joined = joined.filter(filterPredicate).toImmutableList(),
|
||||||
|
banned = banned.filter(filterPredicate).toImmutableList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class RoomMemberWithIdentityState(
|
data class RoomMemberWithIdentityState(
|
||||||
val roomMember: RoomMember,
|
val roomMember: RoomMember,
|
||||||
|
|
|
||||||
|
|
@ -59,36 +59,21 @@ private fun roomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState().copy(
|
aRoomMemberListState().copy(
|
||||||
isSearchActive = false,
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState().copy(
|
aRoomMemberListState().copy(
|
||||||
isSearchActive = true,
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState().copy(
|
aRoomMemberListState().copy(
|
||||||
isSearchActive = true,
|
|
||||||
searchQuery = "someone",
|
searchQuery = "someone",
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState().copy(
|
aRoomMemberListState().copy(
|
||||||
isSearchActive = true,
|
|
||||||
searchQuery = "@someone:matrix.org",
|
searchQuery = "@someone:matrix.org",
|
||||||
searchResults = SearchBarResultState.Results(
|
|
||||||
AsyncData.Success(
|
|
||||||
RoomMembers(
|
|
||||||
invited = persistentListOf(aVictor().withIdentity()),
|
|
||||||
joined = persistentListOf(anAlice().withIdentity()),
|
|
||||||
banned = persistentListOf(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState().copy(
|
aRoomMemberListState().copy(
|
||||||
isSearchActive = true,
|
|
||||||
searchQuery = "something-with-no-results",
|
searchQuery = "something-with-no-results",
|
||||||
searchResults = SearchBarResultState.NoResultsFound(),
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
),
|
),
|
||||||
aRoomMemberListState(
|
aRoomMemberListState(
|
||||||
|
|
@ -143,17 +128,14 @@ private fun bannedRoomMemberListStates(): Sequence<RoomMemberListState> = sequen
|
||||||
|
|
||||||
internal fun aRoomMemberListState(
|
internal fun aRoomMemberListState(
|
||||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||||
searchResults: SearchBarResultState<AsyncData<RoomMembers>> = SearchBarResultState.Initial(),
|
|
||||||
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||||
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
||||||
) = RoomMemberListState(
|
) = RoomMemberListState(
|
||||||
roomMembers = roomMembers,
|
roomMembers = roomMembers,
|
||||||
searchQuery = "",
|
searchQuery = "",
|
||||||
searchResults = searchResults,
|
|
||||||
isSearchActive = false,
|
|
||||||
canInvite = false,
|
canInvite = false,
|
||||||
moderationState = moderationState,
|
moderationState = moderationState,
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
selectedSection = selectedSection,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
import io.element.android.libraries.designsystem.theme.components.SearchField
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SegmentedButton
|
import io.element.android.libraries.designsystem.theme.components.SegmentedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
|
@ -79,44 +78,39 @@ fun RoomMemberListView(
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
if (!state.isSearchActive) {
|
RoomMemberListTopBar(
|
||||||
RoomMemberListTopBar(
|
canInvite = state.canInvite,
|
||||||
canInvite = state.canInvite,
|
onBackClick = navigator::exitRoomMemberList,
|
||||||
onBackClick = navigator::exitRoomMemberList,
|
onInviteClick = navigator::openInviteMembers,
|
||||||
onInviteClick = navigator::openInviteMembers,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding),
|
.consumeWindowInsets(padding),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
RoomMemberSearchBar(
|
var searchQuery by textFieldState(state.searchQuery)
|
||||||
query = state.searchQuery,
|
SearchField(
|
||||||
state = state.searchResults,
|
value = searchQuery,
|
||||||
active = state.isSearchActive,
|
onValueChange = { newQuery ->
|
||||||
placeHolderTitle = stringResource(CommonStrings.common_search_for_someone),
|
searchQuery = newQuery
|
||||||
onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery))
|
||||||
onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
},
|
||||||
onSelectUser = ::onSelectUser,
|
modifier = Modifier
|
||||||
selectedSection = state.selectedSection,
|
.fillMaxWidth()
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.padding(horizontal = 16.dp),
|
||||||
|
placeholder = stringResource(CommonStrings.common_search_for_someone),
|
||||||
|
)
|
||||||
|
RoomMemberList(
|
||||||
|
roomMembers = state.roomMembers,
|
||||||
|
selectedSection = state.selectedSection,
|
||||||
|
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) },
|
||||||
|
showSections = state.moderationState.canBan,
|
||||||
|
onSelectUser = ::onSelectUser,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!state.isSearchActive) {
|
|
||||||
RoomMemberList(
|
|
||||||
roomMembers = state.roomMembers,
|
|
||||||
showMembersCount = true,
|
|
||||||
canDisplayBannedUsersControls = state.moderationState.canBan,
|
|
||||||
selectedSection = state.selectedSection,
|
|
||||||
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) },
|
|
||||||
onSelectUser = ::onSelectUser,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,25 +118,24 @@ fun RoomMemberListView(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomMemberList(
|
private fun RoomMemberList(
|
||||||
roomMembers: AsyncData<RoomMembers>,
|
roomMembers: AsyncData<RoomMembers>,
|
||||||
showMembersCount: Boolean,
|
|
||||||
selectedSection: SelectedSection,
|
selectedSection: SelectedSection,
|
||||||
|
showSections: Boolean = true,
|
||||||
onSelectedSectionChange: (SelectedSection) -> Unit,
|
onSelectedSectionChange: (SelectedSection) -> Unit,
|
||||||
canDisplayBannedUsersControls: Boolean,
|
|
||||||
onSelectUser: (RoomMember) -> Unit,
|
onSelectUser: (RoomMember) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
||||||
stickyHeader {
|
stickyHeader {
|
||||||
Column {
|
Column {
|
||||||
if (canDisplayBannedUsersControls) {
|
if (showSections) {
|
||||||
val segmentedButtonTitles = persistentListOf(
|
val segmentedButtonTitles = persistentListOf(
|
||||||
stringResource(id = R.string.screen_room_member_list_mode_members),
|
stringResource(id = R.string.screen_room_member_list_mode_members),
|
||||||
stringResource(id = R.string.screen_room_member_list_mode_banned),
|
stringResource(id = R.string.screen_room_member_list_mode_banned),
|
||||||
)
|
)
|
||||||
SingleChoiceSegmentedButtonRow(
|
SingleChoiceSegmentedButtonRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(ElementTheme.colors.bgCanvasDefault)
|
.background(ElementTheme.colors.bgCanvasDefault)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||||
) {
|
) {
|
||||||
for ((index, title) in segmentedButtonTitles.withIndex()) {
|
for ((index, title) in segmentedButtonTitles.withIndex()) {
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
|
|
@ -171,7 +164,6 @@ private fun RoomMemberList(
|
||||||
roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn,
|
roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn,
|
||||||
selectedSection = selectedSection,
|
selectedSection = selectedSection,
|
||||||
onSelectUser = onSelectUser,
|
onSelectUser = onSelectUser,
|
||||||
showMembersCount = showMembersCount,
|
|
||||||
)
|
)
|
||||||
AsyncData.Uninitialized -> Unit
|
AsyncData.Uninitialized -> Unit
|
||||||
}
|
}
|
||||||
|
|
@ -182,27 +174,29 @@ private fun LazyListScope.memberItems(
|
||||||
roomMembers: RoomMembers,
|
roomMembers: RoomMembers,
|
||||||
selectedSection: SelectedSection,
|
selectedSection: SelectedSection,
|
||||||
onSelectUser: (RoomMember) -> Unit,
|
onSelectUser: (RoomMember) -> Unit,
|
||||||
showMembersCount: Boolean,
|
|
||||||
) {
|
) {
|
||||||
when (selectedSection) {
|
when (selectedSection) {
|
||||||
SelectedSection.MEMBERS -> {
|
SelectedSection.MEMBERS -> {
|
||||||
if (roomMembers.invited.isNotEmpty()) {
|
if (roomMembers.invited.isNotEmpty()) {
|
||||||
roomMemberListSection(
|
roomMemberListSectionHeader(
|
||||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
text = {
|
||||||
|
val memberCount = roomMembers.invited.count()
|
||||||
|
stringResource(id = R.string.screen_room_member_list_pending_header_title, memberCount)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
roomMemberListSectionItems(
|
||||||
members = roomMembers.invited,
|
members = roomMembers.invited,
|
||||||
onMemberSelected = { onSelectUser(it) }
|
onMemberSelected = { onSelectUser(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (roomMembers.joined.isNotEmpty()) {
|
if (roomMembers.joined.isNotEmpty()) {
|
||||||
roomMemberListSection(
|
roomMemberListSectionHeader(
|
||||||
headerText = {
|
text = {
|
||||||
if (showMembersCount) {
|
val memberCount = roomMembers.joined.count()
|
||||||
val memberCount = roomMembers.joined.count()
|
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
||||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
|
||||||
} else {
|
|
||||||
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
roomMemberListSectionItems(
|
||||||
members = roomMembers.joined,
|
members = roomMembers.joined,
|
||||||
onMemberSelected = { onSelectUser(it) }
|
onMemberSelected = { onSelectUser(it) }
|
||||||
)
|
)
|
||||||
|
|
@ -210,8 +204,14 @@ private fun LazyListScope.memberItems(
|
||||||
}
|
}
|
||||||
SelectedSection.BANNED -> { // Banned users
|
SelectedSection.BANNED -> { // Banned users
|
||||||
if (roomMembers.banned.isNotEmpty()) {
|
if (roomMembers.banned.isNotEmpty()) {
|
||||||
roomMemberListSection(
|
roomMemberListSectionHeader(
|
||||||
headerText = null,
|
text = {
|
||||||
|
val memberCount = roomMembers.banned.count()
|
||||||
|
stringResource(id = R.string.screen_room_member_list_banned_header_title, memberCount)
|
||||||
|
},
|
||||||
|
isCritical = true,
|
||||||
|
)
|
||||||
|
roomMemberListSectionItems(
|
||||||
members = roomMembers.banned,
|
members = roomMembers.banned,
|
||||||
onMemberSelected = { onSelectUser(it) }
|
onMemberSelected = { onSelectUser(it) }
|
||||||
)
|
)
|
||||||
|
|
@ -219,13 +219,13 @@ private fun LazyListScope.memberItems(
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillParentMaxSize()
|
.fillParentMaxSize()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 56.dp)
|
.padding(bottom = 56.dp)
|
||||||
.align(Alignment.Center),
|
.align(Alignment.Center),
|
||||||
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
@ -241,8 +241,8 @@ private fun LazyListScope.failureItem(failure: Throwable) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 32.dp),
|
.padding(horizontal = 16.dp, vertical = 32.dp),
|
||||||
text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage,
|
text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage,
|
||||||
color = ElementTheme.colors.textCriticalPrimary,
|
color = ElementTheme.colors.textCriticalPrimary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
@ -250,21 +250,25 @@ private fun LazyListScope.failureItem(failure: Throwable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LazyListScope.roomMemberListSection(
|
private fun LazyListScope.roomMemberListSectionHeader(
|
||||||
headerText: @Composable (() -> String)?,
|
text: @Composable (() -> String),
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isCritical: Boolean = false,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
modifier = modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
text = text(),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = if (isCritical) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.roomMemberListSectionItems(
|
||||||
members: ImmutableList<RoomMemberWithIdentityState>?,
|
members: ImmutableList<RoomMemberWithIdentityState>?,
|
||||||
onMemberSelected: (RoomMember) -> Unit,
|
onMemberSelected: (RoomMember) -> Unit,
|
||||||
) {
|
) {
|
||||||
headerText?.let {
|
|
||||||
item {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
text = it(),
|
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
|
||||||
color = ElementTheme.colors.textSecondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items(members.orEmpty()) { matrixUser ->
|
items(members.orEmpty()) { matrixUser ->
|
||||||
RoomMemberListItem(
|
RoomMemberListItem(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -353,44 +357,6 @@ private fun RoomMemberListTopBar(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun RoomMemberSearchBar(
|
|
||||||
query: String,
|
|
||||||
state: SearchBarResultState<AsyncData<RoomMembers>>,
|
|
||||||
active: Boolean,
|
|
||||||
placeHolderTitle: String,
|
|
||||||
onActiveChange: (Boolean) -> Unit,
|
|
||||||
onTextChange: (String) -> Unit,
|
|
||||||
onSelectUser: (RoomMember) -> Unit,
|
|
||||||
selectedSection: SelectedSection,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
var queryFieldState by textFieldState(query)
|
|
||||||
SearchBar(
|
|
||||||
query = queryFieldState,
|
|
||||||
onQueryChange = { newQuery ->
|
|
||||||
queryFieldState = newQuery
|
|
||||||
onTextChange(newQuery)
|
|
||||||
},
|
|
||||||
active = active,
|
|
||||||
onActiveChange = onActiveChange,
|
|
||||||
modifier = modifier,
|
|
||||||
placeHolderTitle = placeHolderTitle,
|
|
||||||
resultState = state,
|
|
||||||
resultHandler = { results ->
|
|
||||||
RoomMemberList(
|
|
||||||
roomMembers = results,
|
|
||||||
showMembersCount = false,
|
|
||||||
onSelectUser = { onSelectUser(it) },
|
|
||||||
canDisplayBannedUsersControls = false,
|
|
||||||
selectedSection = selectedSection,
|
|
||||||
onSelectedSectionChange = {},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = ElementPreview {
|
internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,10 @@
|
||||||
<string name="screen_room_details_topic_title">"Topic"</string>
|
<string name="screen_room_details_topic_title">"Topic"</string>
|
||||||
<string name="screen_room_details_updating_room">"Updating room…"</string>
|
<string name="screen_room_details_updating_room">"Updating room…"</string>
|
||||||
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
|
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
|
||||||
|
<string name="screen_room_member_list_banned_header_title">"%1$d Banned"</string>
|
||||||
<plurals name="screen_room_member_list_header_title">
|
<plurals name="screen_room_member_list_header_title">
|
||||||
<item quantity="one">"%1$d person"</item>
|
<item quantity="one">"%1$d Person"</item>
|
||||||
<item quantity="other">"%1$d people"</item>
|
<item quantity="other">"%1$d People"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"Ban user"</string>
|
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"Ban user"</string>
|
||||||
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"Only remove member"</string>
|
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"Only remove member"</string>
|
||||||
|
|
@ -82,7 +83,8 @@
|
||||||
<string name="screen_room_member_list_manage_member_unban_title">"Unban user"</string>
|
<string name="screen_room_member_list_manage_member_unban_title">"Unban user"</string>
|
||||||
<string name="screen_room_member_list_mode_banned">"Banned"</string>
|
<string name="screen_room_member_list_mode_banned">"Banned"</string>
|
||||||
<string name="screen_room_member_list_mode_members">"Members"</string>
|
<string name="screen_room_member_list_mode_members">"Members"</string>
|
||||||
<string name="screen_room_member_list_pending_header_title">"Pending"</string>
|
<string name="screen_room_member_list_pending_header_title">"%1$d Invited"</string>
|
||||||
|
<string name="screen_room_member_list_pending_status">"Pending"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
||||||
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
||||||
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@
|
||||||
"screen_room_details_.*",
|
"screen_room_details_.*",
|
||||||
"screen\\.room_details\\..*",
|
"screen\\.room_details\\..*",
|
||||||
"screen_room_member_list_.*",
|
"screen_room_member_list_.*",
|
||||||
|
"screen\\.room_member_list\\..*",
|
||||||
"screen_room_notification_settings_.*",
|
"screen_room_notification_settings_.*",
|
||||||
"screen_notification_settings_edit_failed_updating_default_mode",
|
"screen_notification_settings_edit_failed_updating_default_mode",
|
||||||
"screen_polls_history_title",
|
"screen_polls_history_title",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue