Fix API break: use new methods canUserRedactOwn and canUserRedactOther.

This commit is contained in:
Benoit Marty 2024-01-23 12:20:07 +01:00 committed by Benoit Marty
parent 123df68736
commit cdbfe8f92c
13 changed files with 286 additions and 49 deletions

View file

@ -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.api.user.CurrentSessionIdHolder
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo 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.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.matrix.ui.room.canSendMessageAsState
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -138,7 +139,8 @@ class MessagesPresenter @AssistedInject constructor(
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) 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 userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value)
val roomName: AsyncData<String> by remember { val roomName: AsyncData<String> by remember {
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
@ -219,7 +221,8 @@ class MessagesPresenter @AssistedInject constructor(
roomName = roomName, roomName = roomName,
roomAvatar = roomAvatar, roomAvatar = roomAvatar,
userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedact = userHasPermissionToRedact, userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
userHasPermissionToSendReaction = userHasPermissionToSendReaction, userHasPermissionToSendReaction = userHasPermissionToSendReaction,
composerState = composerState, composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState, voiceMessageComposerState = voiceMessageComposerState,

View file

@ -36,7 +36,8 @@ data class MessagesState(
val roomName: AsyncData<String>, val roomName: AsyncData<String>,
val roomAvatar: AsyncData<AvatarData>, val roomAvatar: AsyncData<AvatarData>,
val userHasPermissionToSendMessage: Boolean, val userHasPermissionToSendMessage: Boolean,
val userHasPermissionToRedact: Boolean, val userHasPermissionToRedactOwn: Boolean,
val userHasPermissionToRedactOther: Boolean,
val userHasPermissionToSendReaction: Boolean, val userHasPermissionToSendReaction: Boolean,
val composerState: MessageComposerState, val composerState: MessageComposerState,
val voiceMessageComposerState: VoiceMessageComposerState, val voiceMessageComposerState: VoiceMessageComposerState,

View file

@ -86,7 +86,8 @@ fun aMessagesState() = MessagesState(
roomName = AsyncData.Success("Room name"), roomName = AsyncData.Success("Room name"),
roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)), roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userHasPermissionToSendMessage = true, userHasPermissionToSendMessage = true,
userHasPermissionToRedact = false, userHasPermissionToRedactOwn = false,
userHasPermissionToRedactOther = false,
userHasPermissionToSendReaction = true, userHasPermissionToSendReaction = true,
composerState = aMessageComposerState().copy( composerState = aMessageComposerState().copy(
richTextEditorState = RichTextEditorState("Hello", initialFocus = true), richTextEditorState = RichTextEditorState("Hello", initialFocus = true),

View file

@ -160,7 +160,8 @@ fun MessagesView(
state.actionListState.eventSink( state.actionListState.eventSink(
ActionListEvents.ComputeForMessage( ActionListEvents.ComputeForMessage(
event = event, event = event,
canRedact = state.userHasPermissionToRedact, canRedactOwn = state.userHasPermissionToRedactOwn,
canRedactOther = state.userHasPermissionToRedactOther,
canSendMessage = state.userHasPermissionToSendMessage, canSendMessage = state.userHasPermissionToSendMessage,
canSendReaction = state.userHasPermissionToSendReaction, canSendReaction = state.userHasPermissionToSendReaction,
) )

View file

@ -22,7 +22,8 @@ sealed interface ActionListEvents {
data object Clear : ActionListEvents data object Clear : ActionListEvents
data class ComputeForMessage( data class ComputeForMessage(
val event: TimelineItem.Event, val event: TimelineItem.Event,
val canRedact: Boolean, val canRedactOwn: Boolean,
val canRedactOther: Boolean,
val canSendMessage: Boolean, val canSendMessage: Boolean,
val canSendReaction: Boolean, val canSendReaction: Boolean,
) : ActionListEvents ) : ActionListEvents

View file

@ -56,7 +56,8 @@ class ActionListPresenter @Inject constructor(
ActionListEvents.Clear -> target.value = ActionListState.Target.None ActionListEvents.Clear -> target.value = ActionListState.Target.None
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
timelineItem = event.event, timelineItem = event.event,
userCanRedact = event.canRedact, userCanRedactOwn = event.canRedactOwn,
userCanRedactOther = event.canRedactOther,
userCanSendMessage = event.canSendMessage, userCanSendMessage = event.canSendMessage,
userCanSendReaction = event.canSendReaction, userCanSendReaction = event.canSendReaction,
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
@ -73,13 +74,16 @@ class ActionListPresenter @Inject constructor(
private fun CoroutineScope.computeForMessage( private fun CoroutineScope.computeForMessage(
timelineItem: TimelineItem.Event, timelineItem: TimelineItem.Event,
userCanRedact: Boolean, userCanRedactOwn: Boolean,
userCanRedactOther: Boolean,
userCanSendMessage: Boolean, userCanSendMessage: Boolean,
userCanSendReaction: Boolean, userCanSendReaction: Boolean,
isDeveloperModeEnabled: Boolean, isDeveloperModeEnabled: Boolean,
target: MutableState<ActionListState.Target> target: MutableState<ActionListState.Target>
) = launch { ) = launch {
target.value = ActionListState.Target.Loading(timelineItem) target.value = ActionListState.Target.Loading(timelineItem)
val canRedact = (timelineItem.isMine && userCanRedactOwn) ||
(!timelineItem.isMine && userCanRedactOther)
val actions = val actions =
when (timelineItem.content) { when (timelineItem.content) {
is TimelineItemRedactedContent -> { is TimelineItemRedactedContent -> {
@ -99,7 +103,6 @@ class ActionListPresenter @Inject constructor(
} }
is TimelineItemPollContent -> { is TimelineItemPollContent -> {
buildList { buildList {
val isMineOrCanRedact = timelineItem.isMine || userCanRedact
if (timelineItem.isRemote) { if (timelineItem.isRemote) {
// Can only reply or forward messages already uploaded to the server // Can only reply or forward messages already uploaded to the server
add(TimelineItemAction.Reply) add(TimelineItemAction.Reply)
@ -107,7 +110,7 @@ class ActionListPresenter @Inject constructor(
if (timelineItem.isRemote && timelineItem.isEditable) { if (timelineItem.isRemote && timelineItem.isEditable) {
add(TimelineItemAction.Edit) add(TimelineItemAction.Edit)
} }
if (timelineItem.isRemote && !timelineItem.content.isEnded && isMineOrCanRedact) { if (timelineItem.isRemote && !timelineItem.content.isEnded && (timelineItem.isMine || canRedact)) {
add(TimelineItemAction.EndPoll) add(TimelineItemAction.EndPoll)
} }
if (timelineItem.content.canBeCopied()) { if (timelineItem.content.canBeCopied()) {
@ -119,7 +122,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) { if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent) add(TimelineItemAction.ReportContent)
} }
if (isMineOrCanRedact) { if (canRedact) {
add(TimelineItemAction.Redact) add(TimelineItemAction.Redact)
} }
} }
@ -136,7 +139,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) { if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent) add(TimelineItemAction.ReportContent)
} }
if (timelineItem.isMine || userCanRedact) { if (canRedact) {
add(TimelineItemAction.Redact) add(TimelineItemAction.Redact)
} }
} }
@ -169,7 +172,7 @@ class ActionListPresenter @Inject constructor(
if (!timelineItem.isMine) { if (!timelineItem.isMine) {
add(TimelineItemAction.ReportContent) add(TimelineItemAction.ReportContent)
} }
if (timelineItem.isMine || userCanRedact) { if (canRedact) {
add(TimelineItemAction.Redact) add(TimelineItemAction.Redact)
} }
} }

View file

@ -120,7 +120,7 @@ class MessagesPresenterTest {
assertThat(initialState.roomAvatar) assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) .isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue() assertThat(initialState.userHasPermissionToSendMessage).isTrue()
assertThat(initialState.userHasPermissionToRedact).isFalse() assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
assertThat(initialState.hasNetworkConnection).isTrue() assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
@ -601,14 +601,29 @@ class MessagesPresenterTest {
} }
@Test @Test
fun `present - permission to redact`() = runTest { fun `present - permission to redact own`() = runTest {
val matrixRoom = FakeMatrixRoom(canRedact = true) val matrixRoom = FakeMatrixRoom(canRedactOwn = true)
val presenter = createMessagesPresenter(matrixRoom = matrixRoom) val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedact }.last() val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last()
assertThat(initialState.userHasPermissionToRedact).isTrue() 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() cancelAndIgnoreRemainingEvents()
} }
} }

View file

@ -61,7 +61,15 @@ class ActionListPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent(isMine = true, content = TimelineItemRedactedContent) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -87,7 +95,15 @@ class ActionListPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent(isMine = false, content = TimelineItemRedactedContent) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -116,7 +132,15 @@ class ActionListPresenterTest {
isMine = false, isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -149,7 +173,15 @@ class ActionListPresenterTest {
isMine = false, isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -181,7 +213,15 @@ class ActionListPresenterTest {
isMine = false, isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -213,7 +253,15 @@ class ActionListPresenterTest {
isMine = false, isMine = false,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -245,7 +293,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() 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 @Test
fun `present - compute for a media item`() = runTest { fun `present - compute for a media item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
@ -279,7 +376,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = aTimelineItemImageContent(), 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -311,7 +416,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = aTimelineItemStateEventContent(), 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -341,7 +454,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = aTimelineItemStateEventContent(), 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -370,7 +491,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null) 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() // val loadingState = awaitItem()
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
val successState = awaitItem() val successState = awaitItem()
@ -408,10 +537,26 @@ class ActionListPresenterTest {
content = TimelineItemRedactedContent, 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) 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 { awaitItem().run {
assertThat(target).isEqualTo(ActionListState.Target.None) assertThat(target).isEqualTo(ActionListState.Target.None)
} }
@ -432,7 +577,15 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null), 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -460,7 +613,15 @@ class ActionListPresenterTest {
isEditable = true, isEditable = true,
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)), 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -489,7 +650,15 @@ class ActionListPresenterTest {
isEditable = false, isEditable = false,
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)), 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -517,7 +686,15 @@ class ActionListPresenterTest {
isEditable = false, isEditable = false,
content = aTimelineItemPollContent(isEnded = true), 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(
@ -543,7 +720,15 @@ class ActionListPresenterTest {
isMine = true, isMine = true,
content = aTimelineItemVoiceContent(), 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() val successState = awaitItem()
assertThat(successState.target).isEqualTo( assertThat(successState.target).isEqualTo(
ActionListState.Target.Success( ActionListState.Target.Success(

View file

@ -127,7 +127,9 @@ interface MatrixRoom : Closeable {
suspend fun canUserInvite(userId: UserId): Result<Boolean> suspend fun canUserInvite(userId: UserId): Result<Boolean>
suspend fun canUserRedact(userId: UserId): Result<Boolean> suspend fun canUserRedactOwn(userId: UserId): Result<Boolean>
suspend fun canUserRedactOther(userId: UserId): Result<Boolean>
suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean>

View file

@ -36,6 +36,11 @@ suspend fun MatrixRoom.canSendState(type: StateEventType): Result<Boolean> = can
suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result<Boolean> = canUserSendMessage(sessionId, type) suspend fun MatrixRoom.canSendMessage(type: MessageEventType): Result<Boolean> = 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<Boolean> = canUserRedact(sessionId) suspend fun MatrixRoom.canRedactOwn(): Result<Boolean> = canUserRedactOwn(sessionId)
/**
* Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
*/
suspend fun MatrixRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)

View file

@ -335,9 +335,15 @@ class RustMatrixRoom(
} }
} }
override suspend fun canUserRedact(userId: UserId): Result<Boolean> { override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> {
return runCatching { return runCatching {
innerRoom.canUserRedact(userId.value) innerRoom.canUserRedactOwn(userId.value)
}
}
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> {
return runCatching {
innerRoom.canUserRedactOther(userId.value)
} }
} }

View file

@ -78,7 +78,8 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L, override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
canRedact: Boolean = false, canRedactOwn: Boolean = false,
canRedactOther: Boolean = false,
) : MatrixRoom { ) : MatrixRoom {
private var ignoreResult: Result<Unit> = Result.success(Unit) private var ignoreResult: Result<Unit> = Result.success(Unit)
private var unignoreResult: Result<Unit> = Result.success(Unit) private var unignoreResult: Result<Unit> = Result.success(Unit)
@ -88,7 +89,8 @@ 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 canRedactResult = Result.success(canRedact) private var canRedactOwnResult = Result.success(canRedactOwn)
private var canRedactOtherResult = Result.success(canRedactOther)
private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>() private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>()
private val canSendEventResults = mutableMapOf<MessageEventType, Result<Boolean>>() private val canSendEventResults = mutableMapOf<MessageEventType, Result<Boolean>>()
private var sendMediaResult = Result.success(FakeMediaUploadHandler()) private var sendMediaResult = Result.success(FakeMediaUploadHandler())
@ -276,8 +278,12 @@ class FakeMatrixRoom(
return canInviteResult return canInviteResult
} }
override suspend fun canUserRedact(userId: UserId): Result<Boolean> { override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> {
return canRedactResult return canRedactOwnResult
}
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> {
return canRedactOtherResult
} }
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> { override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {

View file

@ -21,7 +21,8 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
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.MessageEventType 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 import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
@Composable @Composable
@ -32,8 +33,15 @@ fun MatrixRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): S
} }
@Composable @Composable
fun MatrixRoom.canRedactAsState(updateKey: Long): State<Boolean> { fun MatrixRoom.canRedactOwnAsState(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) { return produceState(initialValue = false, key1 = updateKey) {
value = canRedact().getOrElse { false } value = canRedactOwn().getOrElse { false }
}
}
@Composable
fun MatrixRoom.canRedactOtherAsState(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) {
value = canRedactOther().getOrElse { false }
} }
} }