Render data of blocked users (behind deactivated feature flag).

This commit is contained in:
Benoit Marty 2024-05-28 14:32:54 +02:00 committed by Benoit Marty
parent 053ef9b9ab
commit 687b30bd5f
7 changed files with 84 additions and 18 deletions

View file

@ -21,20 +21,26 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class BlockedUsersPresenter @Inject constructor( class BlockedUsersPresenter @Inject constructor(
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val featureFlagService: FeatureFlagService,
) : Presenter<BlockedUsersState> { ) : Presenter<BlockedUsersState> {
@Composable @Composable
override fun present(): BlockedUsersState { override fun present(): BlockedUsersState {
@ -47,7 +53,24 @@ class BlockedUsersPresenter @Inject constructor(
mutableStateOf(AsyncAction.Uninitialized) mutableStateOf(AsyncAction.Uninitialized)
} }
val renderBlockedUsersDetail = featureFlagService
.isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails)
.collectAsState(initial = false)
val ignoredUserIds by matrixClient.ignoredUsersFlow.collectAsState() val ignoredUserIds by matrixClient.ignoredUsersFlow.collectAsState()
val ignoredMatrixUser by produceState(
initialValue = ignoredUserIds.map { MatrixUser(userId = it) },
key1 = renderBlockedUsersDetail.value,
key2 = ignoredUserIds
) {
value = ignoredUserIds.map {
if (renderBlockedUsersDetail.value) {
matrixClient.getProfile(it).getOrNull()
} else {
null
}
?: MatrixUser(userId = it)
}
}
fun handleEvents(event: BlockedUsersEvents) { fun handleEvents(event: BlockedUsersEvents) {
when (event) { when (event) {
@ -68,7 +91,7 @@ class BlockedUsersPresenter @Inject constructor(
} }
} }
return BlockedUsersState( return BlockedUsersState(
blockedUsers = ignoredUserIds, blockedUsers = ignoredMatrixUser.toPersistentList(),
unblockUserAction = unblockUserAction.value, unblockUserAction = unblockUserAction.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )

View file

@ -17,11 +17,11 @@
package io.element.android.features.preferences.impl.blockedusers package io.element.android.features.preferences.impl.blockedusers
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
data class BlockedUsersState( data class BlockedUsersState(
val blockedUsers: ImmutableList<UserId>, val blockedUsers: ImmutableList<MatrixUser>,
val unblockUserAction: AsyncAction<Unit>, val unblockUserAction: AsyncAction<Unit>,
val eventSink: (BlockedUsersEvents) -> Unit, val eventSink: (BlockedUsersEvents) -> Unit,
) )

View file

@ -18,7 +18,7 @@ package io.element.android.features.preferences.impl.blockedusers
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
@ -26,6 +26,7 @@ class BlockedUsersStatePreviewProvider : PreviewParameterProvider<BlockedUsersSt
override val values: Sequence<BlockedUsersState> override val values: Sequence<BlockedUsersState>
get() = sequenceOf( get() = sequenceOf(
aBlockedUsersState(), aBlockedUsersState(),
aBlockedUsersState(blockedUsers = aMatrixUserList().map { it.copy(displayName = null, avatarUrl = null) }),
aBlockedUsersState(blockedUsers = emptyList()), aBlockedUsersState(blockedUsers = emptyList()),
aBlockedUsersState(unblockUserAction = AsyncAction.Confirming), aBlockedUsersState(unblockUserAction = AsyncAction.Confirming),
// Sadly there's no good way to preview Loading or Failure states since they're presented with an animation // Sadly there's no good way to preview Loading or Failure states since they're presented with an animation
@ -37,7 +38,7 @@ class BlockedUsersStatePreviewProvider : PreviewParameterProvider<BlockedUsersSt
} }
internal fun aBlockedUsersState( internal fun aBlockedUsersState(
blockedUsers: List<UserId> = aMatrixUserList().map { it.userId }, blockedUsers: List<MatrixUser> = aMatrixUserList(),
unblockUserAction: AsyncAction<Unit> = AsyncAction.Uninitialized, unblockUserAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
): BlockedUsersState { ): BlockedUsersState {
return BlockedUsersState( return BlockedUsersState(

View file

@ -73,9 +73,9 @@ fun BlockedUsersView(
LazyColumn( LazyColumn(
modifier = Modifier.padding(padding) modifier = Modifier.padding(padding)
) { ) {
items(state.blockedUsers) { userId -> items(state.blockedUsers) { matrixUser ->
BlockedUserItem( BlockedUserItem(
userId = userId, matrixUser = matrixUser,
onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) } onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) }
) )
} }
@ -121,12 +121,12 @@ fun BlockedUsersView(
@Composable @Composable
private fun BlockedUserItem( private fun BlockedUserItem(
userId: UserId, matrixUser: MatrixUser,
onClick: (UserId) -> Unit, onClick: (UserId) -> Unit,
) { ) {
MatrixUserRow( MatrixUserRow(
modifier = Modifier.clickable { onClick(userId) }, modifier = Modifier.clickable { onClick(matrixUser.userId) },
matrixUser = MatrixUser(userId), matrixUser = matrixUser,
) )
} }

View file

@ -21,6 +21,11 @@ import app.cash.molecule.moleculeFlow
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.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
@ -52,7 +57,7 @@ class BlockedUsersPresenterTests {
presenter.present() presenter.present()
}.test { }.test {
with(awaitItem()) { with(awaitItem()) {
assertThat(blockedUsers).isEqualTo(persistentListOf(A_USER_ID)) assertThat(blockedUsers).isEqualTo(persistentListOf(MatrixUser(A_USER_ID)))
assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized) assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized)
} }
} }
@ -68,14 +73,39 @@ class BlockedUsersPresenterTests {
presenter.present() presenter.present()
}.test { }.test {
with(awaitItem()) { with(awaitItem()) {
assertThat(blockedUsers).containsAtLeastElementsIn(persistentListOf(A_USER_ID)) assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID)))
assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized)
} }
matrixClient.ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2) matrixClient.ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
skipItems(1)
with(awaitItem()) { with(awaitItem()) {
assertThat(blockedUsers).isEqualTo(persistentListOf(A_USER_ID, A_USER_ID_2)) assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2)))
assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized) }
}
}
@Test
fun `present - blocked users list with data`() = runTest {
val alice = MatrixUser(A_USER_ID, displayName = "Alice", avatarUrl = "aliceAvatar")
val matrixClient = FakeMatrixClient().apply {
ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
givenGetProfileResult(A_USER_ID, Result.success(alice))
givenGetProfileResult(A_USER_ID_2, Result.failure(AN_EXCEPTION))
}
val presenter = aBlockedUsersPresenter(
matrixClient = matrixClient,
featureFlagService = FakeFeatureFlagService().apply {
setFeatureEnabled(FeatureFlags.ShowBlockedUsersDetails, true)
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
with(awaitItem()) {
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2)))
}
// Alice is resolved
with(awaitItem()) {
assertThat(blockedUsers).isEqualTo(listOf(alice, MatrixUser(A_USER_ID_2)))
} }
} }
} }
@ -157,5 +187,9 @@ class BlockedUsersPresenterTests {
private fun aBlockedUsersPresenter( private fun aBlockedUsersPresenter(
matrixClient: FakeMatrixClient = FakeMatrixClient(), matrixClient: FakeMatrixClient = FakeMatrixClient(),
) = BlockedUsersPresenter(matrixClient) featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
) = BlockedUsersPresenter(
matrixClient = matrixClient,
featureFlagService = featureFlagService,
)
} }

View file

@ -81,5 +81,12 @@ enum class FeatureFlags(
description = "Allow user to search for public rooms in their homeserver", description = "Allow user to search for public rooms in their homeserver",
defaultValue = false, defaultValue = false,
isFinished = false, isFinished = false,
) ),
ShowBlockedUsersDetails(
key = "feature.showBlockedUsersDetails",
title = "Show blocked users details",
description = "Show the name and avatar of blocked users in the blocked users list",
defaultValue = false,
isFinished = false,
),
} }

View file

@ -41,6 +41,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
FeatureFlags.Mentions -> true FeatureFlags.Mentions -> true
FeatureFlags.MarkAsUnread -> true FeatureFlags.MarkAsUnread -> true
FeatureFlags.RoomDirectorySearch -> false FeatureFlags.RoomDirectorySearch -> false
FeatureFlags.ShowBlockedUsersDetails -> false
} }
} else { } else {
false false