Remove dependencies to other presenters to RoomMembersModerationPresenter.
Move canDisplayModerationActions from presenter API to the state it emits.
This commit is contained in:
committed by
Benoit Marty
parent
7dce60c756
commit
62cbff0ffe
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@Module
|
||||
@ContributesTo(RoomScope::class)
|
||||
interface RoomDetailsModule {
|
||||
@Binds
|
||||
fun bindRoomMembersModerationPresenter(presenter: RoomMembersModerationPresenter): Presenter<RoomMembersModerationState>
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
@@ -40,7 +40,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val roomMemberListDataSource: RoomMemberListDataSource,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val roomMembersModerationPresenter: RoomMembersModerationPresenter,
|
||||
private val roomMembersModerationPresenter: Presenter<RoomMembersModerationState>,
|
||||
@Assisted private val navigator: RoomMemberListNavigator,
|
||||
) : Presenter<RoomMemberListState> {
|
||||
@AssistedFactory
|
||||
@@ -136,7 +136,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||
is RoomMemberListEvents.RoomMemberSelected -> coroutineScope.launch {
|
||||
if (roomMembersModerationPresenter.canDisplayModerationActions()) {
|
||||
if (roomModerationState.canDisplayModerationActions) {
|
||||
roomModerationState.eventSink(RoomMembersModerationEvents.SelectRoomMember(event.roomMember))
|
||||
} else {
|
||||
navigator.openRoomMemberDetails(event.roomMember.userId)
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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 androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
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
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
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.di.RoomScope
|
||||
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.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class DefaultRoomMembersModerationPresenter @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : RoomMembersModerationPresenter {
|
||||
private var selectedMember by mutableStateOf<RoomMember?>(null)
|
||||
|
||||
private suspend fun canBan() = room.canBan().getOrDefault(false)
|
||||
private suspend fun canKick() = room.canKick().getOrDefault(false)
|
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean {
|
||||
return !room.isDm && (canBan() || canKick())
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMembersModerationState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
|
||||
|
||||
val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val banUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
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 {
|
||||
selectedMember = event.roomMember
|
||||
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.BanUser -> {
|
||||
if (banUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.banUser(it.userId, banUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
banUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.UnbanUser -> {
|
||||
if (unbanUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.Reset -> {
|
||||
selectedMember = null
|
||||
moderationActions = persistentListOf()
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RoomMembersModerationState(
|
||||
selectedRoomMember = selectedMember,
|
||||
actions = moderationActions,
|
||||
kickUserAsyncAction = kickUserAsyncAction.value,
|
||||
banUserAsyncAction = banUserAsyncAction.value,
|
||||
unbanUserAsyncAction = unbanUserAsyncAction.value,
|
||||
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||
eventSink = { handleEvent(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.kickUser(
|
||||
userId: UserId,
|
||||
kickUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(kickUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
|
||||
room.kickUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.banUser(
|
||||
userId: UserId,
|
||||
banUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(banUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
|
||||
room.banUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.unbanUser(
|
||||
userId: UserId,
|
||||
unbanUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(unbanUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
|
||||
room.unbanUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(action: MutableState<AsyncAction<T>>, block: suspend () -> Result<T>) {
|
||||
launch(dispatchers.io) {
|
||||
action.runUpdatingState {
|
||||
val result = block()
|
||||
if (result.isSuccess) {
|
||||
room.membersStateFlow.drop(1).take(1)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,180 @@
|
||||
|
||||
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.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
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
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.di.RoomScope
|
||||
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.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
interface RoomMembersModerationPresenter : Presenter<RoomMembersModerationState> {
|
||||
suspend fun canDisplayModerationActions(): Boolean
|
||||
class RoomMembersModerationPresenter @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomMembersModerationState> {
|
||||
private var selectedMember by mutableStateOf<RoomMember?>(null)
|
||||
|
||||
fun dummyState() = RoomMembersModerationState(
|
||||
selectedRoomMember = null,
|
||||
actions = persistentListOf(),
|
||||
kickUserAsyncAction = AsyncAction.Uninitialized,
|
||||
banUserAsyncAction = AsyncAction.Uninitialized,
|
||||
unbanUserAsyncAction = AsyncAction.Uninitialized,
|
||||
canDisplayBannedUsers = false,
|
||||
eventSink = {}
|
||||
)
|
||||
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 kickUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val banUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
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 {
|
||||
selectedMember = event.roomMember
|
||||
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.BanUser -> {
|
||||
if (banUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.banUser(it.userId, banUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
banUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.UnbanUser -> {
|
||||
if (unbanUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.Reset -> {
|
||||
selectedMember = null
|
||||
moderationActions = persistentListOf()
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RoomMembersModerationState(
|
||||
canDisplayModerationActions = canDisplayModerationActions,
|
||||
selectedRoomMember = selectedMember,
|
||||
actions = moderationActions,
|
||||
kickUserAsyncAction = kickUserAsyncAction.value,
|
||||
banUserAsyncAction = banUserAsyncAction.value,
|
||||
unbanUserAsyncAction = unbanUserAsyncAction.value,
|
||||
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||
eventSink = { handleEvent(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.kickUser(
|
||||
userId: UserId,
|
||||
kickUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(kickUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
|
||||
room.kickUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.banUser(
|
||||
userId: UserId,
|
||||
banUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(banUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
|
||||
room.banUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.unbanUser(
|
||||
userId: UserId,
|
||||
unbanUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(unbanUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
|
||||
room.unbanUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(
|
||||
action: MutableState<AsyncAction<T>>,
|
||||
block: suspend () -> Result<T>
|
||||
) {
|
||||
launch(dispatchers.io) {
|
||||
action.runUpdatingState {
|
||||
val result = block()
|
||||
if (result.isSuccess) {
|
||||
room.membersStateFlow.drop(1).take(1)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class RoomMembersModerationState(
|
||||
val canDisplayModerationActions: Boolean,
|
||||
val selectedRoomMember: RoomMember?,
|
||||
val actions: ImmutableList<ModerationAction>,
|
||||
val kickUserAsyncAction: AsyncAction<Unit>,
|
||||
|
||||
@@ -71,6 +71,7 @@ class RoomMembersModerationStatePreviewProvider : PreviewParameterProvider<RoomM
|
||||
}
|
||||
|
||||
fun aRoomMembersModerationState(
|
||||
canDisplayModerationActions: Boolean = false,
|
||||
selectedRoomMember: RoomMember? = null,
|
||||
actions: List<ModerationAction> = emptyList(),
|
||||
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
@@ -79,6 +80,7 @@ fun aRoomMembersModerationState(
|
||||
canDisplayBannedUsers: Boolean = false,
|
||||
eventSink: (RoomMembersModerationEvents) -> Unit = {},
|
||||
) = RoomMembersModerationState(
|
||||
canDisplayModerationActions = canDisplayModerationActions,
|
||||
selectedRoomMember = selectedRoomMember,
|
||||
actions = actions.toPersistentList(),
|
||||
kickUserAsyncAction = kickUserAsyncAction,
|
||||
|
||||
@@ -19,8 +19,8 @@ import io.element.android.features.roomdetails.impl.members.aRoomMemberList
|
||||
import io.element.android.features.roomdetails.impl.members.aVictor
|
||||
import io.element.android.features.roomdetails.impl.members.aWalter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
|
||||
import io.element.android.features.roomdetails.members.moderation.FakeRoomMembersModerationPresenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -194,9 +195,9 @@ class RoomMemberListPresenterTest {
|
||||
@Test
|
||||
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest {
|
||||
val navigator = FakeRoomMemberListNavigator()
|
||||
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false)
|
||||
val roomMembersModerationStateLambda = { aRoomMembersModerationState(canDisplayModerationActions = false) }
|
||||
val presenter = createPresenter(
|
||||
moderationPresenter = moderationPresenter,
|
||||
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
|
||||
navigator = navigator,
|
||||
matrixRoom = FakeMatrixRoom(
|
||||
updateMembersResult = { Result.success(Unit) },
|
||||
@@ -215,17 +216,15 @@ class RoomMemberListPresenterTest {
|
||||
@Test
|
||||
fun `present - RoomMemberSelected will open the moderation options if the current user can use them`() = runTest {
|
||||
val navigator = FakeRoomMemberListNavigator()
|
||||
var selectRoomMemberCallCounts = 0
|
||||
val capturingState = aRoomMembersModerationState(eventSink = { event ->
|
||||
if (event is RoomMembersModerationEvents.SelectRoomMember) {
|
||||
selectRoomMemberCallCounts++
|
||||
}
|
||||
})
|
||||
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply {
|
||||
givenState(capturingState)
|
||||
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
|
||||
val roomMembersModerationStateLambda = {
|
||||
aRoomMembersModerationState(
|
||||
canDisplayModerationActions = true,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
val presenter = createPresenter(
|
||||
moderationPresenter = moderationPresenter,
|
||||
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
|
||||
navigator = navigator,
|
||||
matrixRoom = FakeMatrixRoom(
|
||||
updateMembersResult = { Result.success(Unit) },
|
||||
@@ -237,7 +236,7 @@ class RoomMemberListPresenterTest {
|
||||
}.test {
|
||||
skipItems(1)
|
||||
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
|
||||
assertThat(selectRoomMemberCallCounts).isEqualTo(1)
|
||||
eventsRecorder.assertSingle(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,12 +265,12 @@ private fun TestScope.createPresenter(
|
||||
updateMembersResult = { Result.success(Unit) }
|
||||
),
|
||||
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
|
||||
moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(),
|
||||
roomMembersModerationStateLambda: () -> RoomMembersModerationState = { aRoomMembersModerationState() },
|
||||
navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {}
|
||||
) = RoomMemberListPresenter(
|
||||
room = matrixRoom,
|
||||
roomMemberListDataSource = roomMemberListDataSource,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
roomMembersModerationPresenter = moderationPresenter,
|
||||
roomMembersModerationPresenter = { roomMembersModerationStateLambda() },
|
||||
navigator = navigator
|
||||
)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.members.moderation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
|
||||
class FakeRoomMembersModerationPresenter(
|
||||
private val canDisplayModerationActions: Boolean = true,
|
||||
) : RoomMembersModerationPresenter {
|
||||
private var state by mutableStateOf(dummyState())
|
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean {
|
||||
return canDisplayModerationActions
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMembersModerationState {
|
||||
return state
|
||||
}
|
||||
|
||||
fun givenState(state: RoomMembersModerationState) {
|
||||
this.state = state
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ 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.DefaultRoomMembersModerationPresenter
|
||||
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
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
@@ -27,20 +27,23 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultRoomMembersModerationPresenterTest {
|
||||
class RoomMembersModerationPresenterTest {
|
||||
@Test
|
||||
fun `canDisplayModerationActions - when room is DM is false`() = runTest {
|
||||
val room = FakeMatrixRoom(isDirect = true, isPublic = true, activeMemberCount = 2).apply {
|
||||
givenRoomInfo(aRoomInfo(isDirect = true, isPublic = false, activeMembersCount = 2))
|
||||
}
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isFalse()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canDisplayModerationActions).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,8 +54,11 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
canKickResult = { Result.success(true) },
|
||||
canBanResult = { Result.success(true) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isTrue()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().canDisplayModerationActions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,8 +68,11 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
activeMemberCount = 10,
|
||||
canBanResult = { Result.success(true) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isTrue()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().canDisplayModerationActions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -74,7 +83,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -101,7 +110,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -125,7 +134,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
canBanResult = { Result.success(true) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -148,7 +157,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
kickUserResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -176,7 +185,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
banUserResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -211,7 +220,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
).apply {
|
||||
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember)))
|
||||
}
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -237,7 +246,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
canBanResult = { Result.success(true) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.USER) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -261,7 +270,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.USER) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -301,12 +310,12 @@ class DefaultRoomMembersModerationPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultRoomMembersModerationPresenter(
|
||||
private fun TestScope.createRoomMembersModerationPresenter(
|
||||
matrixRoom: FakeMatrixRoom = FakeMatrixRoom(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
): DefaultRoomMembersModerationPresenter {
|
||||
return DefaultRoomMembersModerationPresenter(
|
||||
): RoomMembersModerationPresenter {
|
||||
return RoomMembersModerationPresenter(
|
||||
room = matrixRoom,
|
||||
dispatchers = dispatchers,
|
||||
analyticsService = analyticsService,
|
||||
Reference in New Issue
Block a user