Room admins can change user roles (#2423)
Allow Admins to modify room member roles: - Add a 'roles and permissions' option for each room. - Allow promoting users to admins, adding or removing moderators, and demote yourself if you're and admin. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
1d892b4bc8
commit
b9d902e3fe
110 changed files with 2398 additions and 160 deletions
|
|
@ -29,12 +29,19 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
|||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
|
||||
|
|
@ -56,7 +63,7 @@ interface MatrixRoom : Closeable {
|
|||
/** Whether the room is a direct message. */
|
||||
val isDm: Boolean get() = isDirect && isOneToOne
|
||||
|
||||
val roomInfoFlow: Flow<MatrixRoomInfo>
|
||||
val roomInfoFlow: SharedFlow<MatrixRoomInfo>
|
||||
val roomTypingMembersFlow: Flow<List<UserId>>
|
||||
|
||||
/**
|
||||
|
|
@ -91,6 +98,10 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun unsubscribeFromSync()
|
||||
|
||||
suspend fun userRole(userId: UserId): Result<RoomMember.Role>
|
||||
|
||||
suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit>
|
||||
|
||||
suspend fun userDisplayName(userId: UserId): Result<String?>
|
||||
|
||||
suspend fun userAvatarUrl(userId: UserId): Result<String?>
|
||||
|
|
@ -144,6 +155,18 @@ interface MatrixRoom : Closeable {
|
|||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
||||
|
||||
fun usersWithRole(role: RoomMember.Role): Flow<ImmutableList<RoomMember>> {
|
||||
return roomInfoFlow
|
||||
.map { it.userPowerLevels.filter { (_, powerLevel) -> RoomMember.Role.forPowerLevel(powerLevel) == role } }
|
||||
.distinctUntilChanged()
|
||||
.combine(membersStateFlow) { powerLevels, membersState ->
|
||||
membersState.roomMembers()
|
||||
.orEmpty()
|
||||
.filter { powerLevels.containsKey(it.userId) }
|
||||
.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
@Immutable
|
||||
data class MatrixRoomInfo(
|
||||
|
|
@ -39,6 +41,7 @@ data class MatrixRoomInfo(
|
|||
val activeMembersCount: Long,
|
||||
val invitedMembersCount: Long,
|
||||
val joinedMembersCount: Long,
|
||||
val userPowerLevels: ImmutableMap<UserId, Long>,
|
||||
val highlightCount: Long,
|
||||
val notificationCount: Long,
|
||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||
|
|
|
|||
|
|
@ -32,10 +32,20 @@ data class RoomMember(
|
|||
/**
|
||||
* Role of the RoomMember, based on its [powerLevel].
|
||||
*/
|
||||
enum class Role {
|
||||
ADMIN,
|
||||
MODERATOR,
|
||||
USER
|
||||
enum class Role(val powerLevel: Long) {
|
||||
ADMIN(100L),
|
||||
MODERATOR(50L),
|
||||
USER(0L);
|
||||
|
||||
companion object {
|
||||
fun forPowerLevel(powerLevel: Long): Role {
|
||||
return when {
|
||||
powerLevel >= ADMIN.powerLevel -> ADMIN
|
||||
powerLevel >= MODERATOR.powerLevel -> MODERATOR
|
||||
else -> USER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room.powerlevels
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
|
||||
data class UserRoleChange(
|
||||
val userId: UserId,
|
||||
val role: RoomMember.Role,
|
||||
) {
|
||||
val powerLevel: Long = role.powerLevel
|
||||
}
|
||||
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import org.matrix.rustcomponents.sdk.Membership as RustMembership
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
|
||||
|
|
@ -49,6 +52,7 @@ class MatrixRoomInfoMapper(
|
|||
activeMembersCount = it.activeMembersCount.toLong(),
|
||||
invitedMembersCount = it.invitedMembersCount.toLong(),
|
||||
joinedMembersCount = it.joinedMembersCount.toLong(),
|
||||
userPowerLevels = mapPowerLevels(it.userPowerLevels),
|
||||
highlightCount = it.highlightCount.toLong(),
|
||||
notificationCount = it.notificationCount.toLong(),
|
||||
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
|
||||
|
|
@ -69,3 +73,7 @@ fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
|
|||
RustRoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE
|
||||
}
|
||||
|
||||
fun mapPowerLevels(powerLevels: Map<String, Long>): ImmutableMap<UserId, Long> {
|
||||
return powerLevels.mapKeys { (key, _) -> UserId(key) }.toPersistentMap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
|
|
@ -51,6 +53,7 @@ import io.element.android.libraries.matrix.impl.notificationsettings.RustNotific
|
|||
import io.element.android.libraries.matrix.impl.poll.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
|
||||
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
|
||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
|
|
@ -63,8 +66,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||
|
|
@ -74,6 +80,7 @@ import org.matrix.rustcomponents.sdk.RoomListItem
|
|||
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
|
||||
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
||||
import org.matrix.rustcomponents.sdk.TypingNotificationsListener
|
||||
import org.matrix.rustcomponents.sdk.UserPowerLevelUpdate
|
||||
import org.matrix.rustcomponents.sdk.WidgetCapabilities
|
||||
import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider
|
||||
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
|
||||
|
|
@ -102,7 +109,7 @@ class RustMatrixRoom(
|
|||
) : MatrixRoom {
|
||||
override val roomId = RoomId(innerRoom.id())
|
||||
|
||||
override val roomInfoFlow: Flow<MatrixRoomInfo> = mxCallbackFlow {
|
||||
override val roomInfoFlow: SharedFlow<MatrixRoomInfo> = mxCallbackFlow {
|
||||
launch {
|
||||
val initial = innerRoom.roomInfo().use(matrixRoomInfoMapper::map)
|
||||
channel.trySend(initial)
|
||||
|
|
@ -113,6 +120,7 @@ class RustMatrixRoom(
|
|||
}
|
||||
})
|
||||
}
|
||||
.shareIn(sessionCoroutineScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow {
|
||||
launch {
|
||||
|
|
@ -228,6 +236,19 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> {
|
||||
return runCatching {
|
||||
val powerLevelChanges = changes.map { UserPowerLevelUpdate(it.userId.value, it.powerLevel) }
|
||||
innerRoom.updatePowerLevelsForUsers(powerLevelChanges)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.memberAvatarUrl(userId.value)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
|||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
|
|
@ -54,11 +55,14 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific
|
|||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.File
|
||||
|
||||
|
|
@ -86,6 +90,7 @@ class FakeMatrixRoom(
|
|||
private var unignoreResult: Result<Unit> = Result.success(Unit)
|
||||
private var userDisplayNameResult = Result.success<String?>(null)
|
||||
private var userAvatarUrlResult = Result.success<String?>(null)
|
||||
private var userRoleResult = Result.success(RoomMember.Role.USER)
|
||||
private var updateMembersResult: Result<Unit> = Result.success(Unit)
|
||||
private var joinRoomResult = Result.success(Unit)
|
||||
private var inviteUserResult = Result.success(Unit)
|
||||
|
|
@ -100,6 +105,7 @@ class FakeMatrixRoom(
|
|||
private var setTopicResult = Result.success(Unit)
|
||||
private var updateAvatarResult = Result.success(Unit)
|
||||
private var removeAvatarResult = Result.success(Unit)
|
||||
private var updateUserRoleResult = Result.success(Unit)
|
||||
private var toggleReactionResult = Result.success(Unit)
|
||||
private var retrySendMessageResult = Result.success(Unit)
|
||||
private var cancelSendResult = Result.success(Unit)
|
||||
|
|
@ -170,7 +176,7 @@ class FakeMatrixRoom(
|
|||
private var leaveRoomError: Throwable? = null
|
||||
|
||||
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableSharedFlow(replay = 1)
|
||||
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
|
||||
override val roomInfoFlow: SharedFlow<MatrixRoomInfo> = _roomInfoFlow
|
||||
|
||||
private val _roomTypingMembersFlow: MutableSharedFlow<List<UserId>> = MutableSharedFlow(replay = 1)
|
||||
override val roomTypingMembersFlow: Flow<List<UserId>> = _roomTypingMembersFlow
|
||||
|
|
@ -206,6 +212,14 @@ class FakeMatrixRoom(
|
|||
userAvatarUrlResult
|
||||
}
|
||||
|
||||
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> {
|
||||
return userRoleResult
|
||||
}
|
||||
|
||||
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> {
|
||||
return updateUserRoleResult
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>) = simulateLongTask {
|
||||
sendMessageMentions = mentions
|
||||
Result.success(Unit)
|
||||
|
|
@ -496,6 +510,14 @@ class FakeMatrixRoom(
|
|||
userAvatarUrlResult = avatarUrl
|
||||
}
|
||||
|
||||
fun givenUserRoleResult(role: Result<RoomMember.Role>) {
|
||||
userRoleResult = role
|
||||
}
|
||||
|
||||
fun givenUpdateUserRoleResult(result: Result<Unit>) {
|
||||
updateUserRoleResult = result
|
||||
}
|
||||
|
||||
fun givenJoinRoomResult(result: Result<Unit>) {
|
||||
joinRoomResult = result
|
||||
}
|
||||
|
|
@ -668,6 +690,7 @@ fun aRoomInfo(
|
|||
notificationCount: Long = 0,
|
||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||
hasRoomCall: Boolean = false,
|
||||
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
||||
activeRoomCallParticipants: List<String> = emptyList()
|
||||
) = MatrixRoomInfo(
|
||||
id = id,
|
||||
|
|
@ -691,5 +714,6 @@ fun aRoomInfo(
|
|||
notificationCount = notificationCount,
|
||||
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||
hasRoomCall = hasRoomCall,
|
||||
userPowerLevels = userPowerLevels,
|
||||
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun SelectedUser(
|
||||
matrixUser: MatrixUser,
|
||||
canRemove: Boolean,
|
||||
onUserRemoved: (MatrixUser) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -70,24 +71,26 @@ fun SelectedUser(
|
|||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(20.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
.clickable(
|
||||
indication = rememberRipple(),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onUserRemoved(matrixUser) }
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(id = CommonStrings.action_remove),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.padding(2.dp)
|
||||
)
|
||||
if (canRemove) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(20.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
.clickable(
|
||||
indication = rememberRipple(),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onUserRemoved(matrixUser) }
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(id = CommonStrings.action_remove),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.padding(2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +100,17 @@ fun SelectedUser(
|
|||
internal fun SelectedUserPreview() = ElementPreview {
|
||||
SelectedUser(
|
||||
aMatrixUser(),
|
||||
canRemove = true,
|
||||
onUserRemoved = {},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun SelectedUserCannotRemovePreview() = ElementPreview {
|
||||
SelectedUser(
|
||||
aMatrixUser(),
|
||||
canRemove = false,
|
||||
onUserRemoved = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,12 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlin.math.floor
|
||||
|
||||
@Composable
|
||||
fun SelectedUsersList(
|
||||
fun SelectedUsersRowList(
|
||||
selectedUsers: ImmutableList<MatrixUser>,
|
||||
onUserRemoved: (MatrixUser) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
autoScroll: Boolean = false,
|
||||
canDeselect: (MatrixUser) -> Boolean = { true },
|
||||
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
|
@ -105,11 +106,12 @@ fun SelectedUsersList(
|
|||
.fillMaxWidth(),
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
itemsIndexed(selectedUsers.toList()) { index, matrixUser ->
|
||||
itemsIndexed(selectedUsers.toList()) { index, selectedUser ->
|
||||
Layout(
|
||||
content = {
|
||||
SelectedUser(
|
||||
matrixUser = matrixUser,
|
||||
matrixUser = selectedUser,
|
||||
canRemove = canDeselect(selectedUser),
|
||||
onUserRemoved = onUserRemoved,
|
||||
)
|
||||
},
|
||||
|
|
@ -133,7 +135,7 @@ fun SelectedUsersList(
|
|||
internal fun SelectedUsersListPreview() = ElementPreview {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
// Two users that will be visible with no scrolling
|
||||
SelectedUsersList(
|
||||
SelectedUsersRowList(
|
||||
selectedUsers = aMatrixUserList().take(2).toImmutableList(),
|
||||
onUserRemoved = {},
|
||||
modifier = Modifier
|
||||
|
|
@ -143,7 +145,7 @@ internal fun SelectedUsersListPreview() = ElementPreview {
|
|||
|
||||
// Multiple users that don't fit, so will be spaced out per the measure policy
|
||||
for (i in 0..5) {
|
||||
SelectedUsersList(
|
||||
SelectedUsersRowList(
|
||||
selectedUsers = aMatrixUserList().take(6).toImmutableList(),
|
||||
onUserRemoved = {},
|
||||
modifier = Modifier
|
||||
|
|
@ -18,9 +18,12 @@ package io.element.android.libraries.matrix.ui.room
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
|
||||
|
|
@ -45,3 +48,10 @@ fun MatrixRoom.canRedactOtherAsState(updateKey: Long): State<Boolean> {
|
|||
value = canRedactOther().getOrElse { false }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MatrixRoom.isOwnUserAdmin(): Boolean {
|
||||
val roomInfo by roomInfoFlow.collectAsState(initial = null)
|
||||
val powerLevel = roomInfo?.userPowerLevels?.get(sessionId) ?: 0L
|
||||
return RoomMember.Role.forPowerLevel(powerLevel) == RoomMember.Role.ADMIN
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
<string name="action_enter_pin">"Enter PIN"</string>
|
||||
<string name="action_forgot_password">"Forgot password?"</string>
|
||||
<string name="action_forward">"Forward"</string>
|
||||
<string name="action_go_back">"Go back"</string>
|
||||
<string name="action_invite">"Invite"</string>
|
||||
<string name="action_invite_friends">"Invite people"</string>
|
||||
<string name="action_invite_friends_to_app">"Invite people to %1$s"</string>
|
||||
|
|
@ -181,6 +182,7 @@
|
|||
<string name="common_room">"Room"</string>
|
||||
<string name="common_room_name">"Room name"</string>
|
||||
<string name="common_room_name_placeholder">"e.g. your project name"</string>
|
||||
<string name="common_saved_changes">"Saved changes"</string>
|
||||
<string name="common_saving">"Saving"</string>
|
||||
<string name="common_screen_lock">"Screen lock"</string>
|
||||
<string name="common_search_for_someone">"Search for someone"</string>
|
||||
|
|
@ -224,6 +226,8 @@
|
|||
<string name="dialog_title_error">"Error"</string>
|
||||
<string name="dialog_title_success">"Success"</string>
|
||||
<string name="dialog_title_warning">"Warning"</string>
|
||||
<string name="dialog_unsaved_changes_description_android">"Your changes have not been saved. Are you sure you want to go back?"</string>
|
||||
<string name="dialog_unsaved_changes_title">"Save changes?"</string>
|
||||
<string name="error_failed_creating_the_permalink">"Failed creating the permalink"</string>
|
||||
<string name="error_failed_loading_map">"%1$s could not load the map. Please try again later."</string>
|
||||
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue