diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index ddc9f17262..a0a3e3a286 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -72,7 +72,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType -import io.element.android.libraries.matrix.ui.room.canSendEventAsState +import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.MessageComposerMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -108,7 +108,7 @@ class MessagesPresenter @AssistedInject constructor( val retryState = retrySendMenuPresenter.present() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) val roomName by produceState(initialValue = room.displayName, key1 = syncUpdateFlow.value) { value = room.displayName } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 488b6093cd..cb8b536be1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -33,7 +33,7 @@ import io.element.android.libraries.matrix.api.core.EventId 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.timeline.item.event.TimelineItemEventOrigin -import io.element.android.libraries.matrix.ui.room.canSendEventAsState +import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -67,7 +67,7 @@ class TimelinePresenter @Inject constructor( val timelineItems by timelineItemsFactory.collectItemsAsState() val paginationState by timeline.paginationState.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val hasNewItems = remember { mutableStateOf(false) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 4ef482ffe9..2612c24365 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -32,6 +32,8 @@ 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.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.canInvite +import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import javax.inject.Inject @@ -50,9 +52,9 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) - val canEditName by getCanSendStateEvent(membersState, StateEventType.ROOM_NAME) - val canEditAvatar by getCanSendStateEvent(membersState, StateEventType.ROOM_AVATAR) - val canEditTopic by getCanSendStateEvent(membersState, StateEventType.ROOM_TOPIC) + val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) + val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) + val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) val dmMember by room.getDirectRoomMember(membersState) val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) val roomType by getRoomType(dmMember) @@ -117,7 +119,7 @@ class RoomDetailsPresenter @Inject constructor( } @Composable - private fun getCanSendStateEvent(membersState: MatrixRoomMembersState, type: StateEventType) = produceState(false, membersState) { - value = room.canSendStateEvent(type).getOrElse { false } + private fun getCanSendState(membersState: MatrixRoomMembersState, type: StateEventType) = produceState(false, membersState) { + value = room.canSendState(type).getOrElse { false } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt index 079708c560..0024c64268 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor @@ -79,9 +80,9 @@ class RoomDetailsEditPresenter @Inject constructor( var canChangeAvatar by remember { mutableStateOf(false) } LaunchedEffect(Unit) { - canChangeName = room.canSendStateEvent(StateEventType.ROOM_NAME).getOrElse { false } - canChangeTopic = room.canSendStateEvent(StateEventType.ROOM_TOPIC).getOrElse { false } - canChangeAvatar = room.canSendStateEvent(StateEventType.ROOM_AVATAR).getOrElse { false } + canChangeName = room.canSendState(StateEventType.ROOM_NAME).getOrElse { false } + canChangeTopic = room.canSendState(StateEventType.ROOM_TOPIC).getOrElse { false } + canChangeAvatar = room.canSendState(StateEventType.ROOM_AVATAR).getOrElse { false } } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 36128bd638..0787563aed 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -32,6 +33,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul 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.RoomMembershipState +import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.withContext import javax.inject.Inject @@ -52,7 +54,9 @@ class RoomMemberListPresenter @Inject constructor( var isSearchActive by rememberSaveable { mutableStateOf(false) } val membersState by room.membersStateFlow.collectAsState() - val canInvite by getCanInvite(membersState = membersState) + val canInvite by produceState(initialValue = false, key1 = membersState) { + value = room.canInvite().getOrElse { false } + } LaunchedEffect(Unit) { withContext(coroutineDispatchers.io) { @@ -98,13 +102,5 @@ class RoomMemberListPresenter @Inject constructor( ) } - @Composable - private fun getCanInvite(membersState: MatrixRoomMembersState): State { - val canInvite = remember(membersState) { mutableStateOf(false) } - LaunchedEffect(membersState) { - canInvite.value = room.canInvite().getOrElse { false } - } - return canInvite - } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f24e347ce9..be0ff447b3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -99,11 +99,11 @@ interface MatrixRoom : Closeable { suspend fun inviteUserById(id: UserId): Result - suspend fun canInvite(): Result + suspend fun canUserInvite(userId: UserId): Result - suspend fun canSendStateEvent(type: StateEventType): Result + suspend fun canUserSendState(userId: UserId, type: StateEventType): Result - suspend fun canSendEvent(type: MessageEventType): Result + suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result suspend fun updateAvatar(mimeType: String, data: ByteArray): Result @@ -134,3 +134,5 @@ interface MatrixRoom : Closeable { assetType: AssetType? = null, ): Result } + + diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt new file mode 100644 index 0000000000..852401bffc --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.StateEventType + +/** + * Shortcut for calling [MatrixRoom.canUserInvite] with our own user. + */ +suspend fun MatrixRoom.canInvite(): Result = canUserInvite(sessionId) + +/** + * Shortcut for calling [MatrixRoom.canUserSendState] with our own user. + */ +suspend fun MatrixRoom.canSendState(type: StateEventType): Result = canUserSendState(sessionId, type) + +/** + * Shortcut for calling [MatrixRoom.canUserSendMessage] with our own user. + */ +suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result = canUserSendMessage(sessionId, type) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 5505ecbe94..8b1df21845 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -57,7 +57,6 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem -import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle import org.matrix.rustcomponents.sdk.genTransactionId @@ -200,19 +199,17 @@ class RustMatrixRoom( } } - override suspend fun userDisplayName(userId: UserId): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.memberDisplayName(userId.value) - } + override suspend fun userDisplayName(userId: UserId): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.memberDisplayName(userId.value) } + } - override suspend fun userAvatarUrl(userId: UserId): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.memberAvatarUrl(userId.value) - } + override suspend fun userAvatarUrl(userId: UserId): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.memberAvatarUrl(userId.value) } + } override suspend fun sendMessage(message: String): Result = withContext(roomDispatcher) { val transactionId = genTransactionId() @@ -269,21 +266,21 @@ class RustMatrixRoom( } } - override suspend fun canInvite(): Result = withContext(roomMembersDispatcher) { - runCatching { - innerRoom.member(sessionId.value).use(RoomMember::canInvite) + override suspend fun canUserInvite(userId: UserId): Result { + return runCatching { + innerRoom.canUserInvite(userId.value) } } - override suspend fun canSendStateEvent(type: StateEventType): Result = withContext(roomMembersDispatcher) { - runCatching { - innerRoom.member(sessionId.value).use { it.canSendState(type.map()) } + override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result { + return runCatching { + innerRoom.canUserSendState(userId.value, type.map()) } } - override suspend fun canSendEvent(type: MessageEventType): Result = withContext(roomMembersDispatcher) { - runCatching { - innerRoom.member(sessionId.value).use { it.canSendMessage(type.map()) } + override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result { + return runCatching { + innerRoom.canUserSendMessage(userId.value, type.map()) } } @@ -325,48 +322,42 @@ class RustMatrixRoom( } } - override suspend fun retrySendMessage(transactionId: TransactionId): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.retrySend(transactionId.value) - } + override suspend fun retrySendMessage(transactionId: TransactionId): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.retrySend(transactionId.value) } + } - override suspend fun cancelSend(transactionId: TransactionId): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.cancelSend(transactionId.value) - } + override suspend fun cancelSend(transactionId: TransactionId): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.cancelSend(transactionId.value) } + } @OptIn(ExperimentalUnsignedTypes::class) - override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.uploadAvatar(mimeType, data.toUByteArray().toList()) - } + override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.uploadAvatar(mimeType, data.toUByteArray().toList()) } + } - override suspend fun removeAvatar(): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.removeAvatar() - } + override suspend fun removeAvatar(): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.removeAvatar() } + } - override suspend fun setName(name: String): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.setName(name) - } + override suspend fun setName(name: String): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.setName(name) } + } - override suspend fun setTopic(topic: String): Result = - withContext(roomDispatcher) { - runCatching { - innerRoom.setTopic(topic) - } + override suspend fun setTopic(topic: String): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.setTopic(topic) } + } private suspend fun fetchMembers() = withContext(roomDispatcher) { runCatching { @@ -411,4 +402,3 @@ private suspend fun sendAttachment(handle: () -> SendAttachmentJoinHandle): Resu } } } - diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index fc038b3f0c..d2b4d621ed 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo @@ -31,6 +30,7 @@ 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.MessageEventType 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.timeline.MatrixTimeline import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -202,15 +202,15 @@ class FakeMatrixRoom( inviteUserResult } - override suspend fun canInvite(): Result { + override suspend fun canUserInvite(userId: UserId): Result { return canInviteResult } - override suspend fun canSendStateEvent(type: StateEventType): Result { + override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result { return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer")) } - override suspend fun canSendEvent(type: MessageEventType): Result { + override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result { return canSendEventResults[type] ?: Result.failure(IllegalStateException("No fake answer")) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index 4533f1f5ef..005a0ac747 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -21,11 +21,12 @@ import androidx.compose.runtime.State 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.powerlevels.canSendMessage @Composable -fun MatrixRoom.canSendEventAsState(type: MessageEventType, updateKey: Long): State { +fun MatrixRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): State { return produceState(initialValue = true, key1 = updateKey) { - value = canSendEvent(type).getOrElse { true } + value = canSendMessage(type).getOrElse { true } } }