Browse Source

Observe ignoredUsersFlow to have live data about blocked user.

This will also ensure that blocking a user will work even if the user is not a member of the room (preparatory work for user permalink)
pull/2721/head
Benoit Marty 5 months ago committed by Benoit Marty
parent
commit
f2ff326938
  1. 45
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt
  2. 31
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt
  3. 5
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

45
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt

@ -39,6 +39,10 @@ import io.element.android.libraries.matrix.api.core.UserId @@ -39,6 +39,10 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class RoomMemberDetailsPresenter @AssistedInject constructor(
@ -57,14 +61,13 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @@ -57,14 +61,13 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
val roomMember by room.getRoomMemberAsState(roomMemberId)
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
// the room member is not really live...
val isBlocked: MutableState<AsyncData<Boolean>> = remember(roomMember) {
val isIgnored = roomMember?.isIgnored
if (isIgnored == null) {
mutableStateOf(AsyncData.Uninitialized)
} else {
mutableStateOf(AsyncData.Success(isIgnored))
}
val isBlocked: MutableState<AsyncData<Boolean>> = remember { mutableStateOf(AsyncData.Uninitialized) }
LaunchedEffect(Unit) {
client.ignoredUsersFlow
.map { ignoredUsers -> roomMemberId in ignoredUsers }
.distinctUntilChanged()
.onEach { isBlocked.value = AsyncData.Success(it) }
.launchIn(this)
}
LaunchedEffect(Unit) {
// Update room member info when opening this screen
@ -132,28 +135,18 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @@ -132,28 +135,18 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
isBlockedState.value = AsyncData.Loading(false)
client.ignoreUser(userId)
.fold(
onSuccess = {
isBlockedState.value = AsyncData.Success(true)
room.getUpdatedMember(userId)
},
onFailure = {
isBlockedState.value = AsyncData.Failure(it, false)
}
)
.onFailure {
isBlockedState.value = AsyncData.Failure(it, false)
}
// Note: on success, ignoredUserList will be updated.
}
private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
isBlockedState.value = AsyncData.Loading(true)
client.unignoreUser(userId)
.fold(
onSuccess = {
isBlockedState.value = AsyncData.Success(false)
room.getUpdatedMember(userId)
},
onFailure = {
isBlockedState.value = AsyncData.Failure(it, true)
}
)
.onFailure {
isBlockedState.value = AsyncData.Failure(it, true)
}
// Note: on success, ignoredUserList will be updated.
}
}

31
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt

@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.members.details @@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.members.details
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.createroom.api.StartDMAction
@ -63,7 +64,7 @@ class RoomMemberDetailsPresenterTests { @@ -63,7 +64,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
assertThat(initialState.userId).isEqualTo(roomMember.userId.value)
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
@ -90,7 +91,7 @@ class RoomMemberDetailsPresenterTests { @@ -90,7 +91,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
@ -113,7 +114,7 @@ class RoomMemberDetailsPresenterTests { @@ -113,7 +114,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
@ -127,7 +128,7 @@ class RoomMemberDetailsPresenterTests { @@ -127,7 +128,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true))
val dialogState = awaitItem()
@ -142,17 +143,24 @@ class RoomMemberDetailsPresenterTests { @@ -142,17 +143,24 @@ class RoomMemberDetailsPresenterTests {
@Test
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
val presenter = createRoomMemberDetailsPresenter()
val client = FakeMatrixClient()
val roomMember = aRoomMember()
val presenter = createRoomMemberDetailsPresenter(
client = client,
roomMember = roomMember,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
client.emitIgnoreUserList(listOf(roomMember.userId))
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
client.emitIgnoreUserList(listOf())
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
}
}
@ -165,7 +173,7 @@ class RoomMemberDetailsPresenterTests { @@ -165,7 +173,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
val errorState = awaitItem()
@ -182,7 +190,7 @@ class RoomMemberDetailsPresenterTests { @@ -182,7 +190,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true))
val dialogState = awaitItem()
@ -202,7 +210,7 @@ class RoomMemberDetailsPresenterTests { @@ -202,7 +210,7 @@ class RoomMemberDetailsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val initialState = awaitFirstItem()
assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java)
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
@ -229,6 +237,11 @@ class RoomMemberDetailsPresenterTests { @@ -229,6 +237,11 @@ class RoomMemberDetailsPresenterTests {
}
}
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
skipItems(1)
return awaitItem()
}
private fun createRoomMemberDetailsPresenter(
client: MatrixClient = FakeMatrixClient(),
room: MatrixRoom = aMatrixRoom(),

5
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.test.verification.FakeSessionVerifica @@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.test.verification.FakeSessionVerifica
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@ -205,6 +206,10 @@ class FakeMatrixClient( @@ -205,6 +206,10 @@ class FakeMatrixClient(
return RoomMembershipObserver()
}
suspend fun emitIgnoreUserList(users: List<UserId>) {
ignoredUsersFlow.emit(users.toImmutableList())
}
// Mocks
fun givenLogoutError(failure: Throwable?) {

Loading…
Cancel
Save