change(members): use SearchField and update strings
This commit is contained in:
parent
0beeda6001
commit
a3bb1b93ab
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_member_list_banned_empty">"There are no banned users."</string>
|
||||
<plurals name="screen_room_member_list_header_title">
|
||||
<item quantity="one">"%1$d person"</item>
|
||||
<item quantity="other">"%1$d people"</item>
|
||||
<item quantity="one">"%1$d Person"</item>
|
||||
<item quantity="other">"%1$d People"</item>
|
||||
</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_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_mode_banned">"Banned"</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_moderator">"Moderator"</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 {
|
||||
data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents
|
||||
data class UpdateSearchQuery(val query: String) : RoomMemberListEvents
|
||||
data class OnSearchActiveChanged(val active: Boolean) : 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.libraries.architecture.AsyncData
|
||||
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.designsystem.theme.components.SearchBarResultState
|
||||
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.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -63,8 +63,6 @@ class RoomMemberListPresenter(
|
|||
var searchResults by remember {
|
||||
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
||||
}
|
||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val membersState by room.membersStateFlow.collectAsState()
|
||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
||||
|
|
@ -78,8 +76,9 @@ class RoomMemberListPresenter(
|
|||
.launchIn(this)
|
||||
}
|
||||
|
||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading())}
|
||||
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS)}
|
||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||
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
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -98,7 +97,7 @@ class RoomMemberListPresenter(
|
|||
}
|
||||
withContext(coroutineDispatchers.io) {
|
||||
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) {
|
||||
// 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.
|
||||
|
|
@ -125,43 +124,16 @@ class RoomMemberListPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(membersState, searchQuery, isSearchActive) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
searchResults = if (searchQuery.isEmpty() || !isSearchActive) {
|
||||
SearchBarResultState.Initial()
|
||||
} 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)
|
||||
}
|
||||
)
|
||||
}
|
||||
LaunchedEffect(searchQuery, roomMembers) {
|
||||
filteredRoomMembers = roomMembers.map { members ->
|
||||
withContext(coroutineDispatchers.io) {
|
||||
members.filter(searchQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvent(event: RoomMemberListEvents) {
|
||||
when (event) {
|
||||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||
is RoomMemberListEvents.RoomMemberSelected ->
|
||||
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
|
||||
|
|
@ -176,10 +148,8 @@ class RoomMemberListPresenter(
|
|||
}
|
||||
|
||||
return RoomMemberListState(
|
||||
roomMembers = roomMembers,
|
||||
roomMembers = filteredRoomMembers,
|
||||
searchQuery = searchQuery,
|
||||
searchResults = searchResults,
|
||||
isSearchActive = isSearchActive,
|
||||
canInvite = canInvite,
|
||||
moderationState = roomModerationState,
|
||||
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.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.room.RoomMember
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
data class RoomMemberListState(
|
||||
val roomMembers: AsyncData<RoomMembers>,
|
||||
val searchQuery: String,
|
||||
val searchResults: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||
val isSearchActive: Boolean,
|
||||
val canInvite: Boolean,
|
||||
val selectedSection: SelectedSection,
|
||||
val moderationState: RoomMemberModerationState,
|
||||
|
|
@ -35,7 +34,22 @@ data class RoomMembers(
|
|||
val invited: ImmutableList<RoomMemberWithIdentityState>,
|
||||
val joined: 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(
|
||||
val roomMember: RoomMember,
|
||||
|
|
|
|||
|
|
@ -59,36 +59,21 @@ private fun roomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
|||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = false,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "someone",
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
searchResults = SearchBarResultState.Results(
|
||||
AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity()),
|
||||
joined = persistentListOf(anAlice().withIdentity()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = SearchBarResultState.NoResultsFound(),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState(
|
||||
|
|
@ -143,17 +128,14 @@ private fun bannedRoomMemberListStates(): Sequence<RoomMemberListState> = sequen
|
|||
|
||||
internal fun aRoomMemberListState(
|
||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||
searchResults: SearchBarResultState<AsyncData<RoomMembers>> = SearchBarResultState.Initial(),
|
||||
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
||||
) = RoomMemberListState(
|
||||
roomMembers = roomMembers,
|
||||
searchQuery = "",
|
||||
searchResults = searchResults,
|
||||
isSearchActive = false,
|
||||
canInvite = false,
|
||||
moderationState = moderationState,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
selectedSection = selectedSection,
|
||||
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.LinearProgressIndicator
|
||||
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.SearchBarResultState
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchField
|
||||
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.TextButton
|
||||
|
|
@ -79,44 +78,39 @@ fun RoomMemberListView(
|
|||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
if (!state.isSearchActive) {
|
||||
RoomMemberListTopBar(
|
||||
canInvite = state.canInvite,
|
||||
onBackClick = navigator::exitRoomMemberList,
|
||||
onInviteClick = navigator::openInviteMembers,
|
||||
)
|
||||
}
|
||||
RoomMemberListTopBar(
|
||||
canInvite = state.canInvite,
|
||||
onBackClick = navigator::exitRoomMemberList,
|
||||
onInviteClick = navigator::openInviteMembers,
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
.fillMaxWidth()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
RoomMemberSearchBar(
|
||||
query = state.searchQuery,
|
||||
state = state.searchResults,
|
||||
active = state.isSearchActive,
|
||||
placeHolderTitle = stringResource(CommonStrings.common_search_for_someone),
|
||||
onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
||||
onSelectUser = ::onSelectUser,
|
||||
selectedSection = state.selectedSection,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
var searchQuery by textFieldState(state.searchQuery)
|
||||
SearchField(
|
||||
value = searchQuery,
|
||||
onValueChange = { newQuery ->
|
||||
searchQuery = newQuery
|
||||
state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery))
|
||||
},
|
||||
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
|
||||
private fun RoomMemberList(
|
||||
roomMembers: AsyncData<RoomMembers>,
|
||||
showMembersCount: Boolean,
|
||||
selectedSection: SelectedSection,
|
||||
showSections: Boolean = true,
|
||||
onSelectedSectionChange: (SelectedSection) -> Unit,
|
||||
canDisplayBannedUsersControls: Boolean,
|
||||
onSelectUser: (RoomMember) -> Unit,
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
||||
stickyHeader {
|
||||
Column {
|
||||
if (canDisplayBannedUsersControls) {
|
||||
if (showSections) {
|
||||
val segmentedButtonTitles = persistentListOf(
|
||||
stringResource(id = R.string.screen_room_member_list_mode_members),
|
||||
stringResource(id = R.string.screen_room_member_list_mode_banned),
|
||||
)
|
||||
SingleChoiceSegmentedButtonRow(
|
||||
modifier = Modifier
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
) {
|
||||
for ((index, title) in segmentedButtonTitles.withIndex()) {
|
||||
SegmentedButton(
|
||||
|
|
@ -171,7 +164,6 @@ private fun RoomMemberList(
|
|||
roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn,
|
||||
selectedSection = selectedSection,
|
||||
onSelectUser = onSelectUser,
|
||||
showMembersCount = showMembersCount,
|
||||
)
|
||||
AsyncData.Uninitialized -> Unit
|
||||
}
|
||||
|
|
@ -182,27 +174,29 @@ private fun LazyListScope.memberItems(
|
|||
roomMembers: RoomMembers,
|
||||
selectedSection: SelectedSection,
|
||||
onSelectUser: (RoomMember) -> Unit,
|
||||
showMembersCount: Boolean,
|
||||
) {
|
||||
when (selectedSection) {
|
||||
SelectedSection.MEMBERS -> {
|
||||
if (roomMembers.invited.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
||||
roomMemberListSectionHeader(
|
||||
text = {
|
||||
val memberCount = roomMembers.invited.count()
|
||||
stringResource(id = R.string.screen_room_member_list_pending_header_title, memberCount)
|
||||
},
|
||||
)
|
||||
roomMemberListSectionItems(
|
||||
members = roomMembers.invited,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
}
|
||||
if (roomMembers.joined.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = {
|
||||
if (showMembersCount) {
|
||||
val memberCount = roomMembers.joined.count()
|
||||
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)
|
||||
}
|
||||
roomMemberListSectionHeader(
|
||||
text = {
|
||||
val memberCount = roomMembers.joined.count()
|
||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
||||
},
|
||||
)
|
||||
roomMemberListSectionItems(
|
||||
members = roomMembers.joined,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
|
|
@ -210,8 +204,14 @@ private fun LazyListScope.memberItems(
|
|||
}
|
||||
SelectedSection.BANNED -> { // Banned users
|
||||
if (roomMembers.banned.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = null,
|
||||
roomMemberListSectionHeader(
|
||||
text = {
|
||||
val memberCount = roomMembers.banned.count()
|
||||
stringResource(id = R.string.screen_room_member_list_banned_header_title, memberCount)
|
||||
},
|
||||
isCritical = true,
|
||||
)
|
||||
roomMemberListSectionItems(
|
||||
members = roomMembers.banned,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
|
|
@ -219,13 +219,13 @@ private fun LazyListScope.memberItems(
|
|||
item {
|
||||
Box(
|
||||
Modifier
|
||||
.fillParentMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillParentMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 56.dp)
|
||||
.align(Alignment.Center),
|
||||
.padding(bottom = 56.dp)
|
||||
.align(Alignment.Center),
|
||||
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
@ -241,8 +241,8 @@ private fun LazyListScope.failureItem(failure: Throwable) {
|
|||
item {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 32.dp),
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 32.dp),
|
||||
text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage,
|
||||
color = ElementTheme.colors.textCriticalPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
@ -250,21 +250,25 @@ private fun LazyListScope.failureItem(failure: Throwable) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.roomMemberListSection(
|
||||
headerText: @Composable (() -> String)?,
|
||||
private fun LazyListScope.roomMemberListSectionHeader(
|
||||
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>?,
|
||||
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 ->
|
||||
RoomMemberListItem(
|
||||
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
|
||||
@Composable
|
||||
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_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_header_title">"%1$d Banned"</string>
|
||||
<plurals name="screen_room_member_list_header_title">
|
||||
<item quantity="one">"%1$d person"</item>
|
||||
<item quantity="other">"%1$d people"</item>
|
||||
<item quantity="one">"%1$d Person"</item>
|
||||
<item quantity="other">"%1$d People"</item>
|
||||
</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_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_mode_banned">"Banned"</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_moderator">"Moderator"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@
|
|||
"screen_room_details_.*",
|
||||
"screen\\.room_details\\..*",
|
||||
"screen_room_member_list_.*",
|
||||
"screen\\.room_member_list\\..*",
|
||||
"screen_room_notification_settings_.*",
|
||||
"screen_notification_settings_edit_failed_updating_default_mode",
|
||||
"screen_polls_history_title",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue