Allow user with enough power level to redact other's messages (#969)
This commit is contained in:
parent
6ad201b08c
commit
b2d3368f63
13 changed files with 112 additions and 18 deletions
|
|
@ -71,6 +71,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.api.room.MessageEventType
|
||||||
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.canSendMessageAsState
|
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -109,6 +110,7 @@ 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)
|
||||||
var roomName: Async<String> by remember { mutableStateOf(Async.Uninitialized) }
|
var roomName: Async<String> by remember { mutableStateOf(Async.Uninitialized) }
|
||||||
var roomAvatar: Async<AvatarData> by remember { mutableStateOf(Async.Uninitialized) }
|
var roomAvatar: Async<AvatarData> by remember { mutableStateOf(Async.Uninitialized) }
|
||||||
LaunchedEffect(syncUpdateFlow.value) {
|
LaunchedEffect(syncUpdateFlow.value) {
|
||||||
|
|
@ -165,6 +167,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||||
|
userHasPermissionToRedact = userHasPermissionToRedact,
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
actionListState = actionListState,
|
actionListState = actionListState,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ data class MessagesState(
|
||||||
val roomName: Async<String>,
|
val roomName: Async<String>,
|
||||||
val roomAvatar: Async<AvatarData>,
|
val roomAvatar: Async<AvatarData>,
|
||||||
val userHasPermissionToSendMessage: Boolean,
|
val userHasPermissionToSendMessage: Boolean,
|
||||||
|
val userHasPermissionToRedact: Boolean,
|
||||||
val composerState: MessageComposerState,
|
val composerState: MessageComposerState,
|
||||||
val timelineState: TimelineState,
|
val timelineState: TimelineState,
|
||||||
val actionListState: ActionListState,
|
val actionListState: ActionListState,
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ fun aMessagesState() = MessagesState(
|
||||||
roomName = Async.Success("Room name"),
|
roomName = Async.Success("Room name"),
|
||||||
roomAvatar = Async.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
roomAvatar = Async.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
||||||
userHasPermissionToSendMessage = true,
|
userHasPermissionToSendMessage = true,
|
||||||
|
userHasPermissionToRedact = false,
|
||||||
composerState = aMessageComposerState().copy(
|
composerState = aMessageComposerState().copy(
|
||||||
text = "Hello",
|
text = "Hello",
|
||||||
isFullScreen = false,
|
isFullScreen = false,
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ fun MessagesView(
|
||||||
fun onMessageLongClicked(event: TimelineItem.Event) {
|
fun onMessageLongClicked(event: TimelineItem.Event) {
|
||||||
Timber.v("OnMessageLongClicked= ${event.id}")
|
Timber.v("OnMessageLongClicked= ${event.id}")
|
||||||
localView.hideKeyboard()
|
localView.hideKeyboard()
|
||||||
state.actionListState.eventSink(ActionListEvents.ComputeForMessage(event))
|
state.actionListState.eventSink(ActionListEvents.ComputeForMessage(event, state.userHasPermissionToRedact))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
|
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
||||||
sealed interface ActionListEvents {
|
sealed interface ActionListEvents {
|
||||||
object Clear : ActionListEvents
|
object Clear : ActionListEvents
|
||||||
data class ComputeForMessage(val event: TimelineItem.Event) : ActionListEvents
|
data class ComputeForMessage(val event: TimelineItem.Event, val canRedact: Boolean) : ActionListEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,11 @@ class ActionListPresenter @Inject constructor(
|
||||||
fun handleEvents(event: ActionListEvents) {
|
fun handleEvents(event: ActionListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||||
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(event.event, target)
|
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
|
||||||
|
timelineItem = event.event,
|
||||||
|
userCanRedact = event.canRedact,
|
||||||
|
target = target,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +69,11 @@ class ActionListPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState<ActionListState.Target>) = launch {
|
private fun CoroutineScope.computeForMessage(
|
||||||
|
timelineItem: TimelineItem.Event,
|
||||||
|
userCanRedact: Boolean,
|
||||||
|
target: MutableState<ActionListState.Target>
|
||||||
|
) = launch {
|
||||||
target.value = ActionListState.Target.Loading(timelineItem)
|
target.value = ActionListState.Target.Loading(timelineItem)
|
||||||
val actions =
|
val actions =
|
||||||
when (timelineItem.content) {
|
when (timelineItem.content) {
|
||||||
|
|
@ -102,7 +110,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
if (!timelineItem.isMine) {
|
if (!timelineItem.isMine) {
|
||||||
add(TimelineItemAction.ReportContent)
|
add(TimelineItemAction.ReportContent)
|
||||||
}
|
}
|
||||||
if (timelineItem.isMine) {
|
if (timelineItem.isMine || userCanRedact) {
|
||||||
add(TimelineItemAction.Redact)
|
add(TimelineItemAction.Redact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,11 @@ import io.element.android.features.messages.media.FakeLocalMediaFactory
|
||||||
import io.element.android.features.messages.utils.messagesummary.FakeMessageSummaryFormatter
|
import io.element.android.features.messages.utils.messagesummary.FakeMessageSummaryFormatter
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
|
|
@ -83,9 +86,16 @@ class MessagesPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
val initialState = consumeItemsUntilTimeout().last()
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
|
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
|
||||||
|
assertThat(initialState.roomName).isEqualTo(Async.Success(""))
|
||||||
|
assertThat(initialState.roomAvatar).isEqualTo(Async.Success(AvatarData(id = A_ROOM_ID.value, name = "", size = AvatarSize.TimelineRoom)))
|
||||||
|
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
|
||||||
|
assertThat(initialState.userHasPermissionToRedact).isFalse()
|
||||||
|
assertThat(initialState.hasNetworkConnection).isTrue()
|
||||||
|
assertThat(initialState.snackbarMessage).isNull()
|
||||||
|
assertThat(initialState.inviteProgress).isEqualTo(Async.Uninitialized)
|
||||||
|
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,6 +541,18 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - permission to redact`() = runTest {
|
||||||
|
val matrixRoom = FakeMatrixRoom(canRedact = true)
|
||||||
|
val presenter = createMessagePresenter(matrixRoom = matrixRoom)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = consumeItemsUntilTimeout().last()
|
||||||
|
assertThat(initialState.userHasPermissionToRedact).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createMessagePresenter(
|
private fun TestScope.createMessagePresenter(
|
||||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||||
matrixRoom: MatrixRoom = FakeMatrixRoom(),
|
matrixRoom: MatrixRoom = FakeMatrixRoom(),
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ 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))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -81,7 +81,7 @@ 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))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -109,7 +109,7 @@ class ActionListPresenterTest {
|
||||||
isMine = false,
|
isMine = false,
|
||||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -130,6 +130,37 @@ class ActionListPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - compute for others message and can redact`() = runTest {
|
||||||
|
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val messageEvent = aMessageEvent(
|
||||||
|
isMine = false,
|
||||||
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, true))
|
||||||
|
val successState = awaitItem()
|
||||||
|
assertThat(successState.target).isEqualTo(
|
||||||
|
ActionListState.Target.Success(
|
||||||
|
messageEvent,
|
||||||
|
persistentListOf(
|
||||||
|
TimelineItemAction.Reply,
|
||||||
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.Developer,
|
||||||
|
TimelineItemAction.ReportContent,
|
||||||
|
TimelineItemAction.Redact,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(ActionListEvents.Clear)
|
||||||
|
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for my message`() = runTest {
|
fun `present - compute for my message`() = runTest {
|
||||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||||
|
|
@ -141,7 +172,7 @@ class ActionListPresenterTest {
|
||||||
isMine = true,
|
isMine = true,
|
||||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -174,7 +205,7 @@ class ActionListPresenterTest {
|
||||||
isMine = true,
|
isMine = true,
|
||||||
content = aTimelineItemImageContent(),
|
content = aTimelineItemImageContent(),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -205,7 +236,7 @@ class ActionListPresenterTest {
|
||||||
isMine = true,
|
isMine = true,
|
||||||
content = aTimelineItemStateEventContent(),
|
content = aTimelineItemStateEventContent(),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -234,7 +265,7 @@ class ActionListPresenterTest {
|
||||||
isMine = true,
|
isMine = true,
|
||||||
content = aTimelineItemStateEventContent(),
|
content = aTimelineItemStateEventContent(),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -262,7 +293,7 @@ class ActionListPresenterTest {
|
||||||
isMine = true,
|
isMine = true,
|
||||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false)
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
// 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()
|
||||||
|
|
@ -299,10 +330,10 @@ class ActionListPresenterTest {
|
||||||
content = TimelineItemRedactedContent,
|
content = TimelineItemRedactedContent,
|
||||||
)
|
)
|
||||||
|
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
||||||
|
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent, false))
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(target).isEqualTo(ActionListState.Target.None)
|
assertThat(target).isEqualTo(ActionListState.Target.None)
|
||||||
assertThat(displayEmojiReactions).isFalse()
|
assertThat(displayEmojiReactions).isFalse()
|
||||||
|
|
@ -323,7 +354,7 @@ class ActionListPresenterTest {
|
||||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false),
|
||||||
)
|
)
|
||||||
|
|
||||||
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent))
|
initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false))
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.target).isEqualTo(
|
assertThat(successState.target).isEqualTo(
|
||||||
ActionListState.Target.Success(
|
ActionListState.Target.Success(
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ 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 canUserSendState(userId: UserId, type: StateEventType): Result<Boolean>
|
suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean>
|
||||||
|
|
||||||
suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean>
|
suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean>
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,9 @@ suspend fun MatrixRoom.canSendState(type: StateEventType): Result<Boolean> = can
|
||||||
* Shortcut for calling [MatrixRoom.canUserSendMessage] with our own user.
|
* Shortcut for calling [MatrixRoom.canUserSendMessage] with our own user.
|
||||||
*/
|
*/
|
||||||
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.
|
||||||
|
*/
|
||||||
|
suspend fun MatrixRoom.canRedact(): Result<Boolean> = canUserRedact(sessionId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,12 @@ class RustMatrixRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserRedact(userId: UserId): Result<Boolean> {
|
||||||
|
return runCatching {
|
||||||
|
innerRoom.canUserRedact(userId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {
|
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
innerRoom.canUserSendState(userId.value, type.map())
|
innerRoom.canUserSendState(userId.value, type.map())
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ class FakeMatrixRoom(
|
||||||
override val joinedMemberCount: Long = 123L,
|
override val joinedMemberCount: Long = 123L,
|
||||||
override val activeMemberCount: Long = 234L,
|
override val activeMemberCount: Long = 234L,
|
||||||
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||||
|
canRedact: Boolean = false,
|
||||||
) : MatrixRoom {
|
) : MatrixRoom {
|
||||||
|
|
||||||
private var ignoreResult: Result<Unit> = Result.success(Unit)
|
private var ignoreResult: Result<Unit> = Result.success(Unit)
|
||||||
|
|
@ -66,6 +67,7 @@ class FakeMatrixRoom(
|
||||||
private var joinRoomResult = Result.success(Unit)
|
private var joinRoomResult = Result.success(Unit)
|
||||||
private var inviteUserResult = Result.success(Unit)
|
private var inviteUserResult = Result.success(Unit)
|
||||||
private var canInviteResult = Result.success(true)
|
private var canInviteResult = Result.success(true)
|
||||||
|
private var canRedactResult = Result.success(canRedact)
|
||||||
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(Unit)
|
private var sendMediaResult = Result.success(Unit)
|
||||||
|
|
@ -207,6 +209,10 @@ class FakeMatrixRoom(
|
||||||
return canInviteResult
|
return canInviteResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserRedact(userId: UserId): Result<Boolean> {
|
||||||
|
return canRedactResult
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {
|
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {
|
||||||
return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer"))
|
return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ 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.canSendMessage
|
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -30,3 +31,10 @@ fun MatrixRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MatrixRoom.canRedactAsState(updateKey: Long): State<Boolean> {
|
||||||
|
return produceState(initialValue = false, key1 = updateKey) {
|
||||||
|
value = canRedact().getOrElse { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue