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)
This commit is contained in:
parent
f2f29c6638
commit
87298803c6
3 changed files with 46 additions and 35 deletions
|
|
@ -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.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
|
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RoomMemberDetailsPresenter @AssistedInject constructor(
|
class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||||
|
|
@ -57,14 +61,13 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||||
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
||||||
val roomMember by room.getRoomMemberAsState(roomMemberId)
|
val roomMember by room.getRoomMemberAsState(roomMemberId)
|
||||||
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
// the room member is not really live...
|
val isBlocked: MutableState<AsyncData<Boolean>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
||||||
val isBlocked: MutableState<AsyncData<Boolean>> = remember(roomMember) {
|
LaunchedEffect(Unit) {
|
||||||
val isIgnored = roomMember?.isIgnored
|
client.ignoredUsersFlow
|
||||||
if (isIgnored == null) {
|
.map { ignoredUsers -> roomMemberId in ignoredUsers }
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
.distinctUntilChanged()
|
||||||
} else {
|
.onEach { isBlocked.value = AsyncData.Success(it) }
|
||||||
mutableStateOf(AsyncData.Success(isIgnored))
|
.launchIn(this)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Update room member info when opening this screen
|
// Update room member info when opening this screen
|
||||||
|
|
@ -132,28 +135,18 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||||
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
||||||
isBlockedState.value = AsyncData.Loading(false)
|
isBlockedState.value = AsyncData.Loading(false)
|
||||||
client.ignoreUser(userId)
|
client.ignoreUser(userId)
|
||||||
.fold(
|
.onFailure {
|
||||||
onSuccess = {
|
isBlockedState.value = AsyncData.Failure(it, false)
|
||||||
isBlockedState.value = AsyncData.Success(true)
|
}
|
||||||
room.getUpdatedMember(userId)
|
// Note: on success, ignoredUserList will be updated.
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
isBlockedState.value = AsyncData.Failure(it, false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
||||||
isBlockedState.value = AsyncData.Loading(true)
|
isBlockedState.value = AsyncData.Loading(true)
|
||||||
client.unignoreUser(userId)
|
client.unignoreUser(userId)
|
||||||
.fold(
|
.onFailure {
|
||||||
onSuccess = {
|
isBlockedState.value = AsyncData.Failure(it, true)
|
||||||
isBlockedState.value = AsyncData.Success(false)
|
}
|
||||||
room.getUpdatedMember(userId)
|
// Note: on success, ignoredUserList will be updated.
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
isBlockedState.value = AsyncData.Failure(it, true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.members.details
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.createroom.api.StartDMAction
|
import io.element.android.features.createroom.api.StartDMAction
|
||||||
|
|
@ -63,7 +64,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.userId).isEqualTo(roomMember.userId.value)
|
assertThat(initialState.userId).isEqualTo(roomMember.userId.value)
|
||||||
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
||||||
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
||||||
|
|
@ -90,7 +91,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
||||||
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
||||||
|
|
||||||
|
|
@ -113,7 +114,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
||||||
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true))
|
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true))
|
||||||
|
|
||||||
val dialogState = awaitItem()
|
val dialogState = awaitItem()
|
||||||
|
|
@ -142,17 +143,24 @@ class RoomMemberDetailsPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
|
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) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
||||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||||
|
client.emitIgnoreUserList(listOf(roomMember.userId))
|
||||||
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
||||||
|
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false))
|
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false))
|
||||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||||
|
client.emitIgnoreUserList(listOf())
|
||||||
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +173,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
||||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||||
val errorState = awaitItem()
|
val errorState = awaitItem()
|
||||||
|
|
@ -182,7 +190,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true))
|
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true))
|
||||||
|
|
||||||
val dialogState = awaitItem()
|
val dialogState = awaitItem()
|
||||||
|
|
@ -202,7 +210,7 @@ class RoomMemberDetailsPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
|
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
|
||||||
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
|
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
|
||||||
|
|
@ -229,6 +237,11 @@ class RoomMemberDetailsPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||||
|
skipItems(1)
|
||||||
|
return awaitItem()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRoomMemberDetailsPresenter(
|
private fun createRoomMemberDetailsPresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
room: MatrixRoom = aMatrixRoom(),
|
room: MatrixRoom = aMatrixRoom(),
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.test.verification.FakeSessionVerifica
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -205,6 +206,10 @@ class FakeMatrixClient(
|
||||||
return RoomMembershipObserver()
|
return RoomMembershipObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun emitIgnoreUserList(users: List<UserId>) {
|
||||||
|
ignoredUsersFlow.emit(users.toImmutableList())
|
||||||
|
}
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
|
|
||||||
fun givenLogoutError(failure: Throwable?) {
|
fun givenLogoutError(failure: Throwable?) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue