From cdbfe8f92c712b7ccb311d3f58324ffb8b9da1d8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 Jan 2024 12:20:07 +0100 Subject: [PATCH] Fix API break: use new methods `canUserRedactOwn` and `canUserRedactOther`. --- .../messages/impl/MessagesPresenter.kt | 9 +- .../features/messages/impl/MessagesState.kt | 3 +- .../messages/impl/MessagesStateProvider.kt | 3 +- .../features/messages/impl/MessagesView.kt | 3 +- .../impl/actionlist/ActionListEvents.kt | 3 +- .../impl/actionlist/ActionListPresenter.kt | 17 +- .../messages/impl/MessagesPresenterTest.kt | 25 +- .../actionlist/ActionListPresenterTest.kt | 221 ++++++++++++++++-- .../libraries/matrix/api/room/MatrixRoom.kt | 4 +- .../room/powerlevels/MatrixRoomPowerLevels.kt | 9 +- .../matrix/impl/room/RustMatrixRoom.kt | 10 +- .../matrix/test/room/FakeMatrixRoom.kt | 14 +- .../matrix/ui/room/MatrixRoomState.kt | 14 +- 13 files changed, 286 insertions(+), 49 deletions(-) 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 e40d0bbb9b..07eeda2efa 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 @@ -84,7 +84,8 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder 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.canRedactAsState +import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState +import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.model.MessageComposerMode import kotlinx.coroutines.CoroutineScope @@ -138,7 +139,8 @@ class MessagesPresenter @AssistedInject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) - val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value) + val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value) + val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value) val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value) val roomName: AsyncData by remember { derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } @@ -219,7 +221,8 @@ class MessagesPresenter @AssistedInject constructor( roomName = roomName, roomAvatar = roomAvatar, userHasPermissionToSendMessage = userHasPermissionToSendMessage, - userHasPermissionToRedact = userHasPermissionToRedact, + userHasPermissionToRedactOwn = userHasPermissionToRedactOwn, + userHasPermissionToRedactOther = userHasPermissionToRedactOther, userHasPermissionToSendReaction = userHasPermissionToSendReaction, composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index c196535607..8e4d1c484b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -36,7 +36,8 @@ data class MessagesState( val roomName: AsyncData, val roomAvatar: AsyncData, val userHasPermissionToSendMessage: Boolean, - val userHasPermissionToRedact: Boolean, + val userHasPermissionToRedactOwn: Boolean, + val userHasPermissionToRedactOther: Boolean, val userHasPermissionToSendReaction: Boolean, val composerState: MessageComposerState, val voiceMessageComposerState: VoiceMessageComposerState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 8fc7466f95..1bb500e08f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -86,7 +86,8 @@ fun aMessagesState() = MessagesState( roomName = AsyncData.Success("Room name"), roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)), userHasPermissionToSendMessage = true, - userHasPermissionToRedact = false, + userHasPermissionToRedactOwn = false, + userHasPermissionToRedactOther = false, userHasPermissionToSendReaction = true, composerState = aMessageComposerState().copy( richTextEditorState = RichTextEditorState("Hello", initialFocus = true), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 96af343a5a..394f1f3556 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -160,7 +160,8 @@ fun MessagesView( state.actionListState.eventSink( ActionListEvents.ComputeForMessage( event = event, - canRedact = state.userHasPermissionToRedact, + canRedactOwn = state.userHasPermissionToRedactOwn, + canRedactOther = state.userHasPermissionToRedactOther, canSendMessage = state.userHasPermissionToSendMessage, canSendReaction = state.userHasPermissionToSendReaction, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt index 6339716ccf..e486c1ae2b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt @@ -22,7 +22,8 @@ sealed interface ActionListEvents { data object Clear : ActionListEvents data class ComputeForMessage( val event: TimelineItem.Event, - val canRedact: Boolean, + val canRedactOwn: Boolean, + val canRedactOther: Boolean, val canSendMessage: Boolean, val canSendReaction: Boolean, ) : ActionListEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 474a0cf022..bede5669fd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -56,7 +56,8 @@ class ActionListPresenter @Inject constructor( ActionListEvents.Clear -> target.value = ActionListState.Target.None is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( timelineItem = event.event, - userCanRedact = event.canRedact, + userCanRedactOwn = event.canRedactOwn, + userCanRedactOther = event.canRedactOther, userCanSendMessage = event.canSendMessage, userCanSendReaction = event.canSendReaction, isDeveloperModeEnabled = isDeveloperModeEnabled, @@ -73,13 +74,16 @@ class ActionListPresenter @Inject constructor( private fun CoroutineScope.computeForMessage( timelineItem: TimelineItem.Event, - userCanRedact: Boolean, + userCanRedactOwn: Boolean, + userCanRedactOther: Boolean, userCanSendMessage: Boolean, userCanSendReaction: Boolean, isDeveloperModeEnabled: Boolean, target: MutableState ) = launch { target.value = ActionListState.Target.Loading(timelineItem) + val canRedact = (timelineItem.isMine && userCanRedactOwn) || + (!timelineItem.isMine && userCanRedactOther) val actions = when (timelineItem.content) { is TimelineItemRedactedContent -> { @@ -99,7 +103,6 @@ class ActionListPresenter @Inject constructor( } is TimelineItemPollContent -> { buildList { - val isMineOrCanRedact = timelineItem.isMine || userCanRedact if (timelineItem.isRemote) { // Can only reply or forward messages already uploaded to the server add(TimelineItemAction.Reply) @@ -107,7 +110,7 @@ class ActionListPresenter @Inject constructor( if (timelineItem.isRemote && timelineItem.isEditable) { add(TimelineItemAction.Edit) } - if (timelineItem.isRemote && !timelineItem.content.isEnded && isMineOrCanRedact) { + if (timelineItem.isRemote && !timelineItem.content.isEnded && (timelineItem.isMine || canRedact)) { add(TimelineItemAction.EndPoll) } if (timelineItem.content.canBeCopied()) { @@ -119,7 +122,7 @@ class ActionListPresenter @Inject constructor( if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) } - if (isMineOrCanRedact) { + if (canRedact) { add(TimelineItemAction.Redact) } } @@ -136,7 +139,7 @@ class ActionListPresenter @Inject constructor( if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) } - if (timelineItem.isMine || userCanRedact) { + if (canRedact) { add(TimelineItemAction.Redact) } } @@ -169,7 +172,7 @@ class ActionListPresenter @Inject constructor( if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) } - if (timelineItem.isMine || userCanRedact) { + if (canRedact) { add(TimelineItemAction.Redact) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 1ad6a693ab..67bbb98e73 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -120,7 +120,7 @@ class MessagesPresenterTest { assertThat(initialState.roomAvatar) .isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) assertThat(initialState.userHasPermissionToSendMessage).isTrue() - assertThat(initialState.userHasPermissionToRedact).isFalse() + assertThat(initialState.userHasPermissionToRedactOwn).isFalse() assertThat(initialState.hasNetworkConnection).isTrue() assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) @@ -601,14 +601,29 @@ class MessagesPresenterTest { } @Test - fun `present - permission to redact`() = runTest { - val matrixRoom = FakeMatrixRoom(canRedact = true) + fun `present - permission to redact own`() = runTest { + val matrixRoom = FakeMatrixRoom(canRedactOwn = true) val presenter = createMessagesPresenter(matrixRoom = matrixRoom) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedact }.last() - assertThat(initialState.userHasPermissionToRedact).isTrue() + val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last() + assertThat(initialState.userHasPermissionToRedactOwn).isTrue() + assertThat(initialState.userHasPermissionToRedactOther).isFalse() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - permission to redact other`() = runTest { + val matrixRoom = FakeMatrixRoom(canRedactOther = true) + val presenter = createMessagesPresenter(matrixRoom = matrixRoom) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOther }.last() + assertThat(initialState.userHasPermissionToRedactOwn).isFalse() + assertThat(initialState.userHasPermissionToRedactOther).isTrue() cancelAndIgnoreRemainingEvents() } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index 0dbdc7194b..1fefc09544 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -61,7 +61,15 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -87,7 +95,15 @@ class ActionListPresenterTest { }.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -116,7 +132,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -149,7 +173,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = false, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = false, + canSendReaction = true + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -181,7 +213,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = true, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -213,7 +253,15 @@ class ActionListPresenterTest { isMine = false, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = true, canSendMessage = true, canSendReaction = false)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = true, + canSendMessage = true, + canSendReaction = false + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -245,7 +293,15 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -268,6 +324,47 @@ class ActionListPresenterTest { } } + @Test + fun `present - compute for my message cannot redact`() = runTest { + val presenter = createActionListPresenter(isDeveloperModeEnabled = true) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = true, + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) + ) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) + // val loadingState = awaitItem() + // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + event = messageEvent, + displayEmojiReactions = true, + actions = persistentListOf( + TimelineItemAction.Reply, + TimelineItemAction.Forward, + TimelineItemAction.Edit, + TimelineItemAction.Copy, + TimelineItemAction.ViewSource, + ) + ) + ) + initialState.eventSink.invoke(ActionListEvents.Clear) + assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) + } + } + @Test fun `present - compute for a media item`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) @@ -279,7 +376,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemImageContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -311,7 +416,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = stateEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -341,7 +454,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemStateEventContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = stateEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -370,7 +491,15 @@ class ActionListPresenterTest { isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) // val loadingState = awaitItem() // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) val successState = awaitItem() @@ -408,10 +537,26 @@ class ActionListPresenterTest { content = TimelineItemRedactedContent, ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = redactedEvent, + canRedactOwn = false, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) awaitItem().run { assertThat(target).isEqualTo(ActionListState.Target.None) } @@ -432,7 +577,15 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -460,7 +613,15 @@ class ActionListPresenterTest { isEditable = true, content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -489,7 +650,15 @@ class ActionListPresenterTest { isEditable = false, content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -517,7 +686,15 @@ class ActionListPresenterTest { isEditable = false, content = aTimelineItemPollContent(isEnded = true), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( @@ -543,7 +720,15 @@ class ActionListPresenterTest { isMine = true, content = aTimelineItemVoiceContent(), ) - initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, canRedact = false, canSendMessage = true, canSendReaction = true)) + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + ) + ) val successState = awaitItem() assertThat(successState.target).isEqualTo( ActionListState.Target.Success( 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 03c143f078..af6cd81fb2 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 @@ -127,7 +127,9 @@ interface MatrixRoom : Closeable { suspend fun canUserInvite(userId: UserId): Result - suspend fun canUserRedact(userId: UserId): Result + suspend fun canUserRedactOwn(userId: UserId): Result + + suspend fun canUserRedactOther(userId: UserId): Result suspend fun canUserSendState(userId: UserId, type: StateEventType): 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 index 3a89f61d9d..c228a9cb2f 100644 --- 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 @@ -36,6 +36,11 @@ suspend fun MatrixRoom.canSendState(type: StateEventType): Result = can suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result = canUserSendMessage(sessionId, type) /** - * Shortcut for calling [MatrixRoom.canUserRedact] with our own user. + * Shortcut for calling [MatrixRoom.canUserRedactOwn] with our own user. */ -suspend fun MatrixRoom.canRedact(): Result = canUserRedact(sessionId) +suspend fun MatrixRoom.canRedactOwn(): Result = canUserRedactOwn(sessionId) + +/** + * Shortcut for calling [MatrixRoom.canRedactOther] with our own user. + */ +suspend fun MatrixRoom.canRedactOther(): Result = canUserRedactOther(sessionId) 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 84ccacb75f..c9a89e558e 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 @@ -335,9 +335,15 @@ class RustMatrixRoom( } } - override suspend fun canUserRedact(userId: UserId): Result { + override suspend fun canUserRedactOwn(userId: UserId): Result { return runCatching { - innerRoom.canUserRedact(userId.value) + innerRoom.canUserRedactOwn(userId.value) + } + } + + override suspend fun canUserRedactOther(userId: UserId): Result { + return runCatching { + innerRoom.canUserRedactOther(userId.value) } } 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 b4e0001e84..fbddf28bbe 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 @@ -78,7 +78,8 @@ class FakeMatrixRoom( override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), - canRedact: Boolean = false, + canRedactOwn: Boolean = false, + canRedactOther: Boolean = false, ) : MatrixRoom { private var ignoreResult: Result = Result.success(Unit) private var unignoreResult: Result = Result.success(Unit) @@ -88,7 +89,8 @@ class FakeMatrixRoom( private var joinRoomResult = Result.success(Unit) private var inviteUserResult = Result.success(Unit) private var canInviteResult = Result.success(true) - private var canRedactResult = Result.success(canRedact) + private var canRedactOwnResult = Result.success(canRedactOwn) + private var canRedactOtherResult = Result.success(canRedactOther) private val canSendStateResults = mutableMapOf>() private val canSendEventResults = mutableMapOf>() private var sendMediaResult = Result.success(FakeMediaUploadHandler()) @@ -276,8 +278,12 @@ class FakeMatrixRoom( return canInviteResult } - override suspend fun canUserRedact(userId: UserId): Result { - return canRedactResult + override suspend fun canUserRedactOwn(userId: UserId): Result { + return canRedactOwnResult + } + + override suspend fun canUserRedactOther(userId: UserId): Result { + return canRedactOtherResult } override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result { 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 dea6195ba8..fbe35742e5 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,7 +21,8 @@ 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.canRedact +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 @Composable @@ -32,8 +33,15 @@ fun MatrixRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): S } @Composable -fun MatrixRoom.canRedactAsState(updateKey: Long): State { +fun MatrixRoom.canRedactOwnAsState(updateKey: Long): State { return produceState(initialValue = false, key1 = updateKey) { - value = canRedact().getOrElse { false } + value = canRedactOwn().getOrElse { false } + } +} + +@Composable +fun MatrixRoom.canRedactOtherAsState(updateKey: Long): State { + return produceState(initialValue = false, key1 = updateKey) { + value = canRedactOther().getOrElse { false } } }