Browse Source

Merge pull request #3671 from element-hq/bma/improveRoomModeration

Improve room moderation
pull/3696/head
Benoit Marty 2 days ago committed by GitHub
parent
commit
f95ab1fb06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt
  2. 3
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt
  3. 98
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt
  4. 2
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt
  5. 23
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt
  6. 51
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt
  7. 7
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationViewTest.kt
  8. 33
      libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt
  9. 4
      tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png
  10. 4
      tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png

15
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.roomdetails.impl.members.moderation
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomMember
data class ConfirmingRoomMemberAction(
val roomMember: RoomMember,
) : AsyncAction.Confirming

3
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt

@ -7,12 +7,13 @@ @@ -7,12 +7,13 @@
package io.element.android.features.roomdetails.impl.members.moderation
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
sealed interface RoomMembersModerationEvents {
data class SelectRoomMember(val roomMember: RoomMember) : RoomMembersModerationEvents
data object KickUser : RoomMembersModerationEvents
data object BanUser : RoomMembersModerationEvents
data object UnbanUser : RoomMembersModerationEvents
data class UnbanUser(val userId: UserId) : RoomMembersModerationEvents
data object Reset : RoomMembersModerationEvents
}

98
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt

@ -10,9 +10,9 @@ package io.element.android.features.roomdetails.impl.members.moderation @@ -10,9 +10,9 @@ package io.element.android.features.roomdetails.impl.members.moderation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@ -21,16 +21,15 @@ import io.element.android.libraries.architecture.AsyncAction @@ -21,16 +21,15 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.finally
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
import io.element.android.libraries.matrix.ui.room.canBanAsState
import io.element.android.libraries.matrix.ui.room.canKickAsState
import io.element.android.libraries.matrix.ui.room.isDmAsState
import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.drop
@ -45,21 +44,39 @@ class RoomMembersModerationPresenter @Inject constructor( @@ -45,21 +44,39 @@ class RoomMembersModerationPresenter @Inject constructor(
) : Presenter<RoomMembersModerationState> {
private var selectedMember by mutableStateOf<RoomMember?>(null)
private suspend fun canBan() = room.canBan().getOrDefault(false)
private suspend fun canKick() = room.canKick().getOrDefault(false)
@Composable
override fun present(): RoomMembersModerationState {
val coroutineScope = rememberCoroutineScope()
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val canDisplayModerationActions by produceState(
initialValue = false,
key1 = syncUpdateFlow.value
) {
value = !room.isDm && (canBan() || canKick())
val canBan by room.canBanAsState(syncUpdateFlow.value)
val canKick by room.canKickAsState(syncUpdateFlow.value)
val isDm by room.isDmAsState(syncUpdateFlow.value)
val currentUserMemberPowerLevel by room.userPowerLevelAsState(syncUpdateFlow.value)
val canDisplayModerationActions by remember {
derivedStateOf { !isDm && (canBan || canKick) }
}
val canDisplayBannedUsers by remember {
derivedStateOf { !isDm && canBan }
}
val moderationActions by remember {
derivedStateOf {
buildList {
selectedMember?.let { roomMember ->
add(ModerationAction.DisplayProfile(roomMember.userId))
if (currentUserMemberPowerLevel > roomMember.powerLevel) {
if (canKick) {
add(ModerationAction.KickUser(roomMember.userId))
}
if (canBan) {
add(ModerationAction.BanUser(roomMember.userId))
}
}
}
}.toPersistentList()
}
}
val kickUserAsyncAction =
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val banUserAsyncAction =
@ -67,64 +84,39 @@ class RoomMembersModerationPresenter @Inject constructor( @@ -67,64 +84,39 @@ class RoomMembersModerationPresenter @Inject constructor(
val unbanUserAsyncAction =
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val canDisplayBannedUsers by produceState(initialValue = false) {
value = !room.isDm && canBan()
}
fun handleEvent(event: RoomMembersModerationEvents) {
when (event) {
is RoomMembersModerationEvents.SelectRoomMember -> {
coroutineScope.launch {
if (event.roomMember.membership == RoomMembershipState.BAN && canBan) {
// In this case the view will render a dialog to confirm the unbanning of the user
unbanUserAsyncAction.value = ConfirmingRoomMemberAction(event.roomMember)
} else {
// In this case the view will render a bottom sheet.
selectedMember = event.roomMember
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams
} else {
moderationActions = buildList {
add(ModerationAction.DisplayProfile(event.roomMember.userId))
val currentUserMemberPowerLevel = room.userRole(room.sessionId)
.getOrDefault(RoomMember.Role.USER)
.powerLevel
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) {
if (canKick()) {
add(ModerationAction.KickUser(event.roomMember.userId))
}
if (canBan()) {
add(ModerationAction.BanUser(event.roomMember.userId))
}
}
}.toPersistentList()
}
}
}
is RoomMembersModerationEvents.KickUser -> {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.kickUser(it.userId, kickUserAsyncAction)
}
selectedMember = null
}
is RoomMembersModerationEvents.BanUser -> {
if (banUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.banUser(it.userId, banUserAsyncAction)
}
selectedMember = null
} else {
banUserAsyncAction.value = AsyncAction.ConfirmingNoParams
}
}
is RoomMembersModerationEvents.UnbanUser -> {
if (unbanUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
}
} else {
unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams
}
// We are already confirming when we are reaching this point
coroutineScope.unbanUser(event.userId, unbanUserAsyncAction)
}
is RoomMembersModerationEvents.Reset -> {
selectedMember = null
moderationActions = persistentListOf()
kickUserAsyncAction.value = AsyncAction.Uninitialized
banUserAsyncAction.value = AsyncAction.Uninitialized
unbanUserAsyncAction.value = AsyncAction.Uninitialized
@ -149,7 +141,7 @@ class RoomMembersModerationPresenter @Inject constructor( @@ -149,7 +141,7 @@ class RoomMembersModerationPresenter @Inject constructor(
kickUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(kickUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
room.kickUser(userId).finally { selectedMember = null }
room.kickUser(userId)
}
private fun CoroutineScope.banUser(
@ -157,7 +149,7 @@ class RoomMembersModerationPresenter @Inject constructor( @@ -157,7 +149,7 @@ class RoomMembersModerationPresenter @Inject constructor(
banUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(banUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
room.banUser(userId).finally { selectedMember = null }
room.banUser(userId)
}
private fun CoroutineScope.unbanUser(
@ -165,7 +157,7 @@ class RoomMembersModerationPresenter @Inject constructor( @@ -165,7 +157,7 @@ class RoomMembersModerationPresenter @Inject constructor(
unbanUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(unbanUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
room.unbanUser(userId).finally { selectedMember = null }
room.unbanUser(userId)
}
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(

2
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt

@ -60,7 +60,7 @@ class RoomMembersModerationStateProvider : PreviewParameterProvider<RoomMembersM @@ -60,7 +60,7 @@ class RoomMembersModerationStateProvider : PreviewParameterProvider<RoomMembersM
),
aRoomMembersModerationState(
selectedRoomMember = anAlice(),
unbanUserAsyncAction = AsyncAction.ConfirmingNoParams,
unbanUserAsyncAction = ConfirmingRoomMemberAction(anAlice()),
),
aRoomMembersModerationState(
kickUserAsyncAction = AsyncAction.Success(Unit),

23
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt

@ -117,7 +117,7 @@ fun RoomMembersModerationView( @@ -117,7 +117,7 @@ fun RoomMembersModerationView(
title = stringResource(R.string.screen_room_member_list_ban_member_confirmation_title),
content = stringResource(R.string.screen_room_member_list_ban_member_confirmation_description),
submitText = stringResource(R.string.screen_room_member_list_ban_member_confirmation_action),
onSubmitClick = { state.selectedRoomMember?.userId?.let { state.eventSink(RoomMembersModerationEvents.BanUser) } },
onSubmitClick = { state.eventSink(RoomMembersModerationEvents.BanUser) },
onDismiss = { state.eventSink(RoomMembersModerationEvents.Reset) }
)
}
@ -147,24 +147,22 @@ fun RoomMembersModerationView( @@ -147,24 +147,22 @@ fun RoomMembersModerationView(
when (val action = state.unbanUserAsyncAction) {
is AsyncAction.Confirming -> {
state.selectedRoomMember?.let {
if (action is ConfirmingRoomMemberAction) {
ConfirmationDialog(
title = stringResource(R.string.screen_room_member_list_manage_member_unban_title),
content = stringResource(R.string.screen_room_member_list_manage_member_unban_message),
submitText = stringResource(R.string.screen_room_member_list_manage_member_unban_action),
onSubmitClick = { state.eventSink(RoomMembersModerationEvents.UnbanUser) },
onSubmitClick = {
val userDisplayName = action.roomMember.getBestName()
asyncIndicatorState.enqueue {
AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName))
}
state.eventSink(RoomMembersModerationEvents.UnbanUser(action.roomMember.userId))
},
onDismiss = { state.eventSink(RoomMembersModerationEvents.Reset) },
)
}
}
is AsyncAction.Loading -> {
LaunchedEffect(action) {
val userDisplayName = state.selectedRoomMember?.getBestName().orEmpty()
asyncIndicatorState.enqueue {
AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName))
}
}
}
is AsyncAction.Failure -> {
Timber.e(action.error, "Failed to unban user.")
LaunchedEffect(action) {
@ -178,7 +176,8 @@ fun RoomMembersModerationView( @@ -178,7 +176,8 @@ fun RoomMembersModerationView(
is AsyncAction.Success -> {
LaunchedEffect(action) { asyncIndicatorState.clear() }
}
else -> Unit
is AsyncAction.Loading,
AsyncAction.Uninitialized -> Unit
}
}
}

51
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt

@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat @@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.roomdetails.impl.members.aVictor
import io.element.android.features.roomdetails.impl.members.moderation.ConfirmingRoomMemberAction
import io.element.android.features.roomdetails.impl.members.moderation.ModerationAction
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
@ -37,7 +38,14 @@ import org.junit.Test @@ -37,7 +38,14 @@ import org.junit.Test
class RoomMembersModerationPresenterTest {
@Test
fun `canDisplayModerationActions - when room is DM is false`() = runTest {
val room = FakeMatrixRoom(isDirect = true, isPublic = true, activeMemberCount = 2).apply {
val room = FakeMatrixRoom(
isDirect = true,
isPublic = true,
activeMemberCount = 2,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
).apply {
givenRoomInfo(aRoomInfo(isDirect = true, isPublic = false, activeMembersCount = 2))
}
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
@ -53,6 +61,7 @@ class RoomMembersModerationPresenterTest { @@ -53,6 +61,7 @@ class RoomMembersModerationPresenterTest {
activeMemberCount = 10,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
presenter.test {
@ -66,7 +75,9 @@ class RoomMembersModerationPresenterTest { @@ -66,7 +75,9 @@ class RoomMembersModerationPresenterTest {
val room = FakeMatrixRoom(
isDirect = false,
activeMemberCount = 10,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
presenter.test {
@ -141,8 +152,8 @@ class RoomMembersModerationPresenterTest { @@ -141,8 +152,8 @@ class RoomMembersModerationPresenterTest {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
with(awaitItem()) {
assertThat(selectedRoomMember).isNotNull()
assertThat(unbanUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams)
assertThat(selectedRoomMember).isNull()
assertThat(unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember))
}
}
}
@ -165,8 +176,9 @@ class RoomMembersModerationPresenterTest { @@ -165,8 +176,9 @@ class RoomMembersModerationPresenterTest {
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
awaitItem().eventSink(RoomMembersModerationEvents.KickUser)
skipItems(1)
assertThat(awaitItem().actions).isEmpty()
assertThat(awaitItem().kickUserAsyncAction).isEqualTo(AsyncAction.Loading)
val loadingState = awaitItem()
assertThat(loadingState.actions).isEmpty()
assertThat(loadingState.kickUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(kickUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(selectedRoomMember).isNull()
@ -198,8 +210,10 @@ class RoomMembersModerationPresenterTest { @@ -198,8 +210,10 @@ class RoomMembersModerationPresenterTest {
// Confirm
confirmingState.eventSink(RoomMembersModerationEvents.BanUser)
skipItems(1)
assertThat(awaitItem().actions).isEmpty()
assertThat(awaitItem().banUserAsyncAction).isEqualTo(AsyncAction.Loading)
val loadingItem = awaitItem()
assertThat(loadingItem.actions).isEmpty()
assertThat(loadingItem.selectedRoomMember).isNull()
assertThat(loadingItem.banUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(banUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(selectedRoomMember).isNull()
@ -225,11 +239,14 @@ class RoomMembersModerationPresenterTest { @@ -225,11 +239,14 @@ class RoomMembersModerationPresenterTest {
presenter.present()
}.test {
skipItems(1)
// Displays confirmation dialog
// Displays unban confirmation dialog
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
val confirmingState = awaitItem()
assertThat(confirmingState.selectedRoomMember).isNull()
assertThat(confirmingState.actions).isEmpty()
assertThat(confirmingState.unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember))
// Confirms unban
awaitItem().eventSink(RoomMembersModerationEvents.UnbanUser)
assertThat(awaitItem().actions).isEmpty()
confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(selectedMember.userId))
assertThat(awaitItem().unbanUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(unbanUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
@ -251,12 +268,13 @@ class RoomMembersModerationPresenterTest { @@ -251,12 +268,13 @@ class RoomMembersModerationPresenterTest {
presenter.present()
}.test {
skipItems(1)
// Displays confirmation dialog
// Select a user
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
// Reset state
awaitItem().eventSink(RoomMembersModerationEvents.Reset)
assertThat(awaitItem().selectedRoomMember).isNull()
assertThat(awaitItem().actions).isEmpty()
val finalItem = awaitItem()
assertThat(finalItem.selectedRoomMember).isNull()
assertThat(finalItem.actions).isEmpty()
}
}
@ -278,7 +296,7 @@ class RoomMembersModerationPresenterTest { @@ -278,7 +296,7 @@ class RoomMembersModerationPresenterTest {
// Kick user and fail
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
awaitItem().eventSink(RoomMembersModerationEvents.KickUser)
skipItems(2)
skipItems(1)
assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it
@ -289,7 +307,7 @@ class RoomMembersModerationPresenterTest { @@ -289,7 +307,7 @@ class RoomMembersModerationPresenterTest {
initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
awaitItem().eventSink(RoomMembersModerationEvents.BanUser)
awaitItem().eventSink(RoomMembersModerationEvents.BanUser)
skipItems(2)
skipItems(1)
assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it
@ -300,8 +318,7 @@ class RoomMembersModerationPresenterTest { @@ -300,8 +318,7 @@ class RoomMembersModerationPresenterTest {
initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor().copy(membership = RoomMembershipState.BAN)))
val confirmingState = awaitItem()
assertThat(confirmingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Confirming::class.java)
confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser)
skipItems(1)
confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(aVictor().userId))
assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it

7
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationViewTest.kt

@ -13,6 +13,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule @@ -13,6 +13,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomdetails.impl.R
import io.element.android.features.roomdetails.impl.members.anAlice
import io.element.android.features.roomdetails.impl.members.moderation.ConfirmingRoomMemberAction
import io.element.android.features.roomdetails.impl.members.moderation.ModerationAction
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
@ -164,7 +165,7 @@ class RoomMembersModerationViewTest { @@ -164,7 +165,7 @@ class RoomMembersModerationViewTest {
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
unbanUserAsyncAction = AsyncAction.ConfirmingNoParams,
unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
@ -181,7 +182,7 @@ class RoomMembersModerationViewTest { @@ -181,7 +182,7 @@ class RoomMembersModerationViewTest {
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
unbanUserAsyncAction = AsyncAction.ConfirmingNoParams,
unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
@ -189,7 +190,7 @@ class RoomMembersModerationViewTest { @@ -189,7 +190,7 @@ class RoomMembersModerationViewTest {
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(R.string.screen_room_member_list_manage_member_unban_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.UnbanUser)
eventsRecorder.assertSingle(RoomMembersModerationEvents.UnbanUser(roomMember.userId))
}
}

33
libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt

@ -15,7 +15,10 @@ import androidx.compose.runtime.produceState @@ -15,7 +15,10 @@ import androidx.compose.runtime.produceState
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
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
@ -62,6 +65,36 @@ fun MatrixRoom.canPinUnpin(updateKey: Long): State<Boolean> { @@ -62,6 +65,36 @@ fun MatrixRoom.canPinUnpin(updateKey: Long): State<Boolean> {
}
}
@Composable
fun MatrixRoom.isDmAsState(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) {
value = isDm
}
}
@Composable
fun MatrixRoom.canKickAsState(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) {
value = canKick().getOrElse { false }
}
}
@Composable
fun MatrixRoom.canBanAsState(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) {
value = canBan().getOrElse { false }
}
}
@Composable
fun MatrixRoom.userPowerLevelAsState(updateKey: Long): State<Long> {
return produceState(initialValue = 0, key1 = updateKey) {
value = userRole(sessionId)
.getOrDefault(RoomMember.Role.USER)
.powerLevel
}
}
@Composable
fun MatrixRoom.isOwnUserAdmin(): Boolean {
val roomInfo by roomInfoFlow.collectAsState(initial = null)

4
tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e0b84ff9cdbc6cc203304ff350789437533f9f7a1d95e8a196cce3585c454ec
size 9143
oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650
size 3642

4
tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b4fd072108b60d09d5c37c56e0e97272a1664ca5d21a27152e111539ac1a640
size 7861
oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd
size 3659

Loading…
Cancel
Save