Display banned users in room member list (#2415)
* Display banned users in room member list --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
cc721b6c87
commit
c5dcd419ce
34 changed files with 197 additions and 31 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
Add moderation to rooms:
|
Add moderation to rooms:
|
||||||
|
|
||||||
- Sort member in room member list by powerlevel, display their roles.
|
- Sort member in room member list by powerlevel, display their roles.
|
||||||
|
- Display banner users in room member list for users with enough power level to ban/unban.
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,18 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
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.core.bool.orFalse
|
||||||
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.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -57,6 +61,20 @@ class RoomMemberListPresenter @Inject constructor(
|
||||||
value = room.canInvite().getOrElse { false }
|
value = room.canInvite().getOrElse { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val canDisplayBannedUsers by produceState(initialValue = false) {
|
||||||
|
val roomIsNotDmAndUserCanBan = !room.isDm && room.canBan().getOrElse { false }
|
||||||
|
if (roomIsNotDmAndUserCanBan) {
|
||||||
|
room.membersStateFlow
|
||||||
|
.onEach { members ->
|
||||||
|
val hasBannedUsers = members.roomMembers()?.any { it.membership == RoomMembershipState.BAN }.orFalse()
|
||||||
|
value = hasBannedUsers
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(membersState) {
|
LaunchedEffect(membersState) {
|
||||||
if (membersState is MatrixRoomMembersState.Unknown) {
|
if (membersState is MatrixRoomMembersState.Unknown) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
|
|
@ -69,6 +87,7 @@ class RoomMemberListPresenter @Inject constructor(
|
||||||
joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||||
.sortedWith(PowerLevelRoomMemberComparator())
|
.sortedWith(PowerLevelRoomMemberComparator())
|
||||||
.toImmutableList(),
|
.toImmutableList(),
|
||||||
|
banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +108,7 @@ class RoomMemberListPresenter @Inject constructor(
|
||||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||||
.sortedWith(PowerLevelRoomMemberComparator())
|
.sortedWith(PowerLevelRoomMemberComparator())
|
||||||
.toImmutableList(),
|
.toImmutableList(),
|
||||||
|
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +122,7 @@ class RoomMemberListPresenter @Inject constructor(
|
||||||
searchResults = searchResults,
|
searchResults = searchResults,
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
|
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||||
eventSink = { event ->
|
eventSink = { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ data class RoomMemberListState(
|
||||||
val searchResults: SearchBarResultState<RoomMembers>,
|
val searchResults: SearchBarResultState<RoomMembers>,
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val canInvite: Boolean,
|
val canInvite: Boolean,
|
||||||
|
val canDisplayBannedUsers: Boolean,
|
||||||
val eventSink: (RoomMemberListEvents) -> Unit,
|
val eventSink: (RoomMemberListEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RoomMembers(
|
data class RoomMembers(
|
||||||
val invited: ImmutableList<RoomMember>,
|
val invited: ImmutableList<RoomMember>,
|
||||||
val joined: ImmutableList<RoomMember>
|
val joined: ImmutableList<RoomMember>,
|
||||||
|
val banned: ImmutableList<RoomMember>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
RoomMembers(
|
RoomMembers(
|
||||||
invited = persistentListOf(aVictor(), aWalter()),
|
invited = persistentListOf(aVictor(), aWalter()),
|
||||||
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
||||||
|
banned = persistentListOf(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
@ -47,6 +48,7 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
RoomMembers(
|
RoomMembers(
|
||||||
invited = persistentListOf(aVictor()),
|
invited = persistentListOf(aVictor()),
|
||||||
joined = persistentListOf(anAlice()),
|
joined = persistentListOf(anAlice()),
|
||||||
|
banned = persistentListOf(),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -55,18 +57,30 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
searchQuery = "something-with-no-results",
|
searchQuery = "something-with-no-results",
|
||||||
searchResults = SearchBarResultState.NoResultsFound()
|
searchResults = SearchBarResultState.NoResultsFound()
|
||||||
),
|
),
|
||||||
|
aRoomMemberListState().copy(
|
||||||
|
roomMembers = AsyncData.Success(
|
||||||
|
RoomMembers(
|
||||||
|
invited = persistentListOf(aVictor(), aWalter()),
|
||||||
|
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
||||||
|
banned = persistentListOf(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
canDisplayBannedUsers = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aRoomMemberListState(
|
internal fun aRoomMemberListState(
|
||||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Uninitialized,
|
roomMembers: AsyncData<RoomMembers> = AsyncData.Uninitialized,
|
||||||
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.Initial(),
|
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.Initial(),
|
||||||
|
canDisplayBannedUsers: Boolean = false,
|
||||||
) = RoomMemberListState(
|
) = RoomMemberListState(
|
||||||
roomMembers = roomMembers,
|
roomMembers = roomMembers,
|
||||||
searchQuery = "",
|
searchQuery = "",
|
||||||
searchResults = searchResults,
|
searchResults = searchResults,
|
||||||
isSearchActive = false,
|
isSearchActive = false,
|
||||||
canInvite = false,
|
canInvite = false,
|
||||||
|
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.members
|
package io.element.android.features.roomdetails.impl.members
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -30,7 +32,12 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
|
@ -49,6 +56,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
|
||||||
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.SearchBar
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
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.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
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
|
@ -58,6 +66,12 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
private enum class SelectedSection {
|
||||||
|
MEMBERS,
|
||||||
|
BANNED
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomMemberListView(
|
fun RoomMemberListView(
|
||||||
|
|
@ -66,6 +80,7 @@ fun RoomMemberListView(
|
||||||
onInvitePressed: () -> Unit,
|
onInvitePressed: () -> Unit,
|
||||||
onMemberSelected: (UserId) -> Unit,
|
onMemberSelected: (UserId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
initialSelectedSectionIndex: Int = 0,
|
||||||
) {
|
) {
|
||||||
fun onUserSelected(roomMember: RoomMember) {
|
fun onUserSelected(roomMember: RoomMember) {
|
||||||
onMemberSelected(roomMember.userId)
|
onMemberSelected(roomMember.userId)
|
||||||
|
|
@ -83,6 +98,7 @@ fun RoomMemberListView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
var selectedSection by remember { mutableStateOf(SelectedSection.entries[initialSelectedSectionIndex]) }
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -98,7 +114,8 @@ fun RoomMemberListView(
|
||||||
onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
||||||
onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
||||||
onUserSelected = ::onUserSelected,
|
onUserSelected = ::onUserSelected,
|
||||||
modifier = Modifier.fillMaxWidth()
|
selectedSection = selectedSection,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!state.isSearchActive) {
|
if (!state.isSearchActive) {
|
||||||
|
|
@ -106,7 +123,10 @@ fun RoomMemberListView(
|
||||||
RoomMemberList(
|
RoomMemberList(
|
||||||
roomMembers = state.roomMembers.data,
|
roomMembers = state.roomMembers.data,
|
||||||
showMembersCount = true,
|
showMembersCount = true,
|
||||||
onUserSelected = ::onUserSelected
|
canDisplayBannedUsersControls = state.canDisplayBannedUsers,
|
||||||
|
selectedSection = selectedSection,
|
||||||
|
onSelectedSectionChanged = { selectedSection = it },
|
||||||
|
onUserSelected = ::onUserSelected,
|
||||||
)
|
)
|
||||||
} else if (state.roomMembers.isLoading()) {
|
} else if (state.roomMembers.isLoading()) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
|
@ -118,49 +138,90 @@ fun RoomMemberListView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomMemberList(
|
private fun RoomMemberList(
|
||||||
roomMembers: RoomMembers,
|
roomMembers: RoomMembers,
|
||||||
showMembersCount: Boolean,
|
showMembersCount: Boolean,
|
||||||
|
selectedSection: SelectedSection,
|
||||||
|
onSelectedSectionChanged: (SelectedSection) -> Unit,
|
||||||
|
canDisplayBannedUsersControls: Boolean,
|
||||||
onUserSelected: (RoomMember) -> Unit,
|
onUserSelected: (RoomMember) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
||||||
if (roomMembers.invited.isNotEmpty()) {
|
if (canDisplayBannedUsersControls) {
|
||||||
roomMemberListSection(
|
stickyHeader {
|
||||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
val segmentedButtonTitles = persistentListOf(
|
||||||
members = roomMembers.invited,
|
stringResource(id = R.string.screen_room_member_list_mode_members),
|
||||||
onMemberSelected = { onUserSelected(it) }
|
stringResource(id = R.string.screen_room_member_list_mode_banned),
|
||||||
)
|
)
|
||||||
}
|
SingleChoiceSegmentedButtonRow(
|
||||||
if (roomMembers.joined.isNotEmpty()) {
|
modifier = Modifier
|
||||||
roomMemberListSection(
|
.background(ElementTheme.colors.bgCanvasDefault)
|
||||||
headerText = {
|
.fillMaxWidth()
|
||||||
if (showMembersCount) {
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||||
val memberCount = roomMembers.joined.count()
|
) {
|
||||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
for ((index, title) in segmentedButtonTitles.withIndex()) {
|
||||||
} else {
|
SegmentedButton(
|
||||||
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
index = index,
|
||||||
|
count = segmentedButtonTitles.size,
|
||||||
|
selected = selectedSection.ordinal == index,
|
||||||
|
onClick = { onSelectedSectionChanged(SelectedSection.entries[index]) },
|
||||||
|
text = title,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
members = roomMembers.joined,
|
}
|
||||||
onMemberSelected = { onUserSelected(it) }
|
}
|
||||||
)
|
when (selectedSection) {
|
||||||
|
SelectedSection.MEMBERS -> {
|
||||||
|
if (roomMembers.invited.isNotEmpty()) {
|
||||||
|
roomMemberListSection(
|
||||||
|
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
||||||
|
members = roomMembers.invited,
|
||||||
|
onMemberSelected = { onUserSelected(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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
members = roomMembers.joined,
|
||||||
|
onMemberSelected = { onUserSelected(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectedSection.BANNED -> { // Banned users
|
||||||
|
roomMemberListSection(
|
||||||
|
headerText = null,
|
||||||
|
members = roomMembers.banned,
|
||||||
|
onMemberSelected = { onUserSelected(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LazyListScope.roomMemberListSection(
|
private fun LazyListScope.roomMemberListSection(
|
||||||
headerText: @Composable () -> String,
|
headerText: @Composable (() -> String)?,
|
||||||
members: ImmutableList<RoomMember>,
|
members: ImmutableList<RoomMember>,
|
||||||
onMemberSelected: (RoomMember) -> Unit,
|
onMemberSelected: (RoomMember) -> Unit,
|
||||||
) {
|
) {
|
||||||
item {
|
headerText?.let {
|
||||||
Text(
|
item {
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
Text(
|
||||||
text = headerText(),
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
text = it(),
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
)
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
items(members) { matrixUser ->
|
items(members) { matrixUser ->
|
||||||
RoomMemberListItem(
|
RoomMemberListItem(
|
||||||
|
|
@ -238,6 +299,7 @@ private fun RoomMemberSearchBar(
|
||||||
onActiveChanged: (Boolean) -> Unit,
|
onActiveChanged: (Boolean) -> Unit,
|
||||||
onTextChanged: (String) -> Unit,
|
onTextChanged: (String) -> Unit,
|
||||||
onUserSelected: (RoomMember) -> Unit,
|
onUserSelected: (RoomMember) -> Unit,
|
||||||
|
selectedSection: SelectedSection,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SearchBar(
|
SearchBar(
|
||||||
|
|
@ -252,7 +314,10 @@ private fun RoomMemberSearchBar(
|
||||||
RoomMemberList(
|
RoomMemberList(
|
||||||
roomMembers = results,
|
roomMembers = results,
|
||||||
showMembersCount = false,
|
showMembersCount = false,
|
||||||
onUserSelected = { onUserSelected(it) }
|
onUserSelected = { onUserSelected(it) },
|
||||||
|
canDisplayBannedUsersControls = false,
|
||||||
|
selectedSection = selectedSection,
|
||||||
|
onSelectedSectionChanged = {},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -268,3 +333,28 @@ internal fun RoomMemberListPreview(@PreviewParameter(RoomMemberListStateProvider
|
||||||
onInvitePressed = {},
|
onInvitePressed = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun RoomMemberBannedListPreview() = ElementPreview {
|
||||||
|
RoomMemberListView(
|
||||||
|
initialSelectedSectionIndex = 1,
|
||||||
|
state = aRoomMemberListState(
|
||||||
|
roomMembers = AsyncData.Success(
|
||||||
|
RoomMembers(
|
||||||
|
invited = persistentListOf(),
|
||||||
|
joined = persistentListOf(),
|
||||||
|
banned = persistentListOf(
|
||||||
|
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||||
|
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||||
|
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
canDisplayBannedUsers = true,
|
||||||
|
),
|
||||||
|
onBackPressed = {},
|
||||||
|
onMemberSelected = {},
|
||||||
|
onInvitePressed = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@
|
||||||
<string name="screen_room_details_notification_title">"Notifications"</string>
|
<string name="screen_room_details_notification_title">"Notifications"</string>
|
||||||
<string name="screen_room_details_share_room_title">"Share room"</string>
|
<string name="screen_room_details_share_room_title">"Share room"</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_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">"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>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<string name="screen_session_verification_compare_numbers_subtitle">"Confirm that the numbers below match those shown on your other session."</string>
|
<string name="screen_session_verification_compare_numbers_subtitle">"Confirm that the numbers below match those shown on your other session."</string>
|
||||||
<string name="screen_session_verification_compare_numbers_title">"Compare numbers"</string>
|
<string name="screen_session_verification_compare_numbers_title">"Compare numbers"</string>
|
||||||
<string name="screen_session_verification_complete_subtitle">"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted."</string>
|
<string name="screen_session_verification_complete_subtitle">"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted."</string>
|
||||||
|
<string name="screen_session_verification_enter_recovery_key">"Enter recovery key"</string>
|
||||||
<string name="screen_session_verification_open_existing_session_subtitle">"Prove it’s you in order to access your encrypted message history."</string>
|
<string name="screen_session_verification_open_existing_session_subtitle">"Prove it’s you in order to access your encrypted message history."</string>
|
||||||
<string name="screen_session_verification_open_existing_session_title">"Open an existing session"</string>
|
<string name="screen_session_verification_open_existing_session_title">"Open an existing session"</string>
|
||||||
<string name="screen_session_verification_positive_button_canceled">"Retry verification"</string>
|
<string name="screen_session_verification_positive_button_canceled">"Retry verification"</string>
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,8 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
suspend fun canUserInvite(userId: UserId): Result<Boolean>
|
suspend fun canUserInvite(userId: UserId): Result<Boolean>
|
||||||
|
|
||||||
|
suspend fun canUserBan(userId: UserId): Result<Boolean>
|
||||||
|
|
||||||
suspend fun canUserRedactOwn(userId: UserId): Result<Boolean>
|
suspend fun canUserRedactOwn(userId: UserId): Result<Boolean>
|
||||||
|
|
||||||
suspend fun canUserRedactOther(userId: UserId): Result<Boolean>
|
suspend fun canUserRedactOther(userId: UserId): Result<Boolean>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
*/
|
*/
|
||||||
suspend fun MatrixRoom.canInvite(): Result<Boolean> = canUserInvite(sessionId)
|
suspend fun MatrixRoom.canInvite(): Result<Boolean> = canUserInvite(sessionId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for calling [MatrixRoom.canBanUser] with our own user.
|
||||||
|
*/
|
||||||
|
suspend fun MatrixRoom.canBan(): Result<Boolean> = canUserBan(sessionId)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for calling [MatrixRoom.canUserSendState] with our own user.
|
* Shortcut for calling [MatrixRoom.canUserSendState] with our own user.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,12 @@ class RustMatrixRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserBan(userId: UserId): Result<Boolean> {
|
||||||
|
return runCatching {
|
||||||
|
innerRoom.canUserBan(userId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> {
|
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
innerRoom.canUserRedactOwn(userId.value)
|
innerRoom.canUserRedactOwn(userId.value)
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class FakeMatrixRoom(
|
||||||
private var joinRoomResult = Result.success(Unit)
|
private var joinRoomResult = Result.success(Unit)
|
||||||
private var inviteUserResult = Result.success(Unit)
|
private var inviteUserResult = Result.success(Unit)
|
||||||
private var canInviteResult = Result.success(true)
|
private var canInviteResult = Result.success(true)
|
||||||
|
private var canBanResult = Result.success(false)
|
||||||
private var canRedactOwnResult = Result.success(canRedactOwn)
|
private var canRedactOwnResult = Result.success(canRedactOwn)
|
||||||
private var canRedactOtherResult = Result.success(canRedactOther)
|
private var canRedactOtherResult = Result.success(canRedactOther)
|
||||||
private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>()
|
private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>()
|
||||||
|
|
@ -280,6 +281,10 @@ class FakeMatrixRoom(
|
||||||
inviteUserResult
|
inviteUserResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserBan(userId: UserId): Result<Boolean> {
|
||||||
|
return canBanResult
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun canUserInvite(userId: UserId): Result<Boolean> {
|
override suspend fun canUserInvite(userId: UserId): Result<Boolean> {
|
||||||
return canInviteResult
|
return canInviteResult
|
||||||
}
|
}
|
||||||
|
|
@ -495,6 +500,10 @@ class FakeMatrixRoom(
|
||||||
joinRoomResult = result
|
joinRoomResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenCanBanResult(result: Result<Boolean>) {
|
||||||
|
canBanResult = result
|
||||||
|
}
|
||||||
|
|
||||||
fun givenInviteUserResult(result: Result<Unit>) {
|
fun givenInviteUserResult(result: Result<Unit>) {
|
||||||
inviteUserResult = result
|
inviteUserResult = result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@
|
||||||
<string name="common_error">"Error"</string>
|
<string name="common_error">"Error"</string>
|
||||||
<string name="common_everyone">"Everyone"</string>
|
<string name="common_everyone">"Everyone"</string>
|
||||||
<string name="common_favourite">"Favourite"</string>
|
<string name="common_favourite">"Favourite"</string>
|
||||||
|
<string name="common_favourited">"Favourited"</string>
|
||||||
<string name="common_file">"File"</string>
|
<string name="common_file">"File"</string>
|
||||||
<string name="common_file_saved_on_disk_android">"File saved to Downloads"</string>
|
<string name="common_file_saved_on_disk_android">"File saved to Downloads"</string>
|
||||||
<string name="common_forward_message">"Forward message"</string>
|
<string name="common_forward_message">"Forward message"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5f6ce6e7d31871ff45ca2494fbb12bb1bc6e44a1fb6baee547b001743d1b4702
|
||||||
|
size 35078
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5b5bd1bfd3ff184ae96c61ee639b0114b4612125f3284360f547fb2bdf73befb
|
||||||
|
size 34121
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:efc266bca406673c57731b4d6a283d217e7199f4b8a9c70c45ed3e00611d165d
|
||||||
|
size 52344
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:65dd3de9356356a9bf651cecd725c1be89a0d1b1ba816e4df9b25ce0a76313dd
|
||||||
|
size 51139
|
||||||
Loading…
Add table
Add a link
Reference in a new issue