diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt index 5424fd737d..5a8d9200e5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt @@ -21,20 +21,26 @@ 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 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.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.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject class BlockedUsersPresenter @Inject constructor( private val matrixClient: MatrixClient, + private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable override fun present(): BlockedUsersState { @@ -47,7 +53,24 @@ class BlockedUsersPresenter @Inject constructor( mutableStateOf(AsyncAction.Uninitialized) } + val renderBlockedUsersDetail = featureFlagService + .isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails) + .collectAsState(initial = false) 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) { when (event) { @@ -68,7 +91,7 @@ class BlockedUsersPresenter @Inject constructor( } } return BlockedUsersState( - blockedUsers = ignoredUserIds, + blockedUsers = ignoredMatrixUser.toPersistentList(), unblockUserAction = unblockUserAction.value, eventSink = ::handleEvents ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt index 8b5209a0cd..42ba3d5a11 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt @@ -17,11 +17,11 @@ package io.element.android.features.preferences.impl.blockedusers 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 data class BlockedUsersState( - val blockedUsers: ImmutableList, + val blockedUsers: ImmutableList, val unblockUserAction: AsyncAction, val eventSink: (BlockedUsersEvents) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewProvider.kt index 0b5466ed04..3057d0132a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.preferences.impl.blockedusers import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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 kotlinx.collections.immutable.toPersistentList @@ -26,6 +26,7 @@ class BlockedUsersStatePreviewProvider : PreviewParameterProvider get() = sequenceOf( aBlockedUsersState(), + aBlockedUsersState(blockedUsers = aMatrixUserList().map { it.copy(displayName = null, avatarUrl = null) }), aBlockedUsersState(blockedUsers = emptyList()), aBlockedUsersState(unblockUserAction = AsyncAction.Confirming), // 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 = aMatrixUserList().map { it.userId }, + blockedUsers: List = aMatrixUserList(), unblockUserAction: AsyncAction = AsyncAction.Uninitialized, ): BlockedUsersState { return BlockedUsersState( diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt index 190950e645..0a5e027beb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt @@ -73,9 +73,9 @@ fun BlockedUsersView( LazyColumn( modifier = Modifier.padding(padding) ) { - items(state.blockedUsers) { userId -> + items(state.blockedUsers) { matrixUser -> BlockedUserItem( - userId = userId, + matrixUser = matrixUser, onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) } ) } @@ -121,12 +121,12 @@ fun BlockedUsersView( @Composable private fun BlockedUserItem( - userId: UserId, + matrixUser: MatrixUser, onClick: (UserId) -> Unit, ) { MatrixUserRow( - modifier = Modifier.clickable { onClick(userId) }, - matrixUser = MatrixUser(userId), + modifier = Modifier.clickable { onClick(matrixUser.userId) }, + matrixUser = matrixUser, ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTests.kt index 3bcd730fed..393776ed87 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTests.kt @@ -21,6 +21,11 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat 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_2 import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -52,7 +57,7 @@ class BlockedUsersPresenterTests { presenter.present() }.test { with(awaitItem()) { - assertThat(blockedUsers).isEqualTo(persistentListOf(A_USER_ID)) + assertThat(blockedUsers).isEqualTo(persistentListOf(MatrixUser(A_USER_ID))) assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -68,14 +73,39 @@ class BlockedUsersPresenterTests { presenter.present() }.test { with(awaitItem()) { - assertThat(blockedUsers).containsAtLeastElementsIn(persistentListOf(A_USER_ID)) - assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID))) } - matrixClient.ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2) + skipItems(1) with(awaitItem()) { - assertThat(blockedUsers).isEqualTo(persistentListOf(A_USER_ID, A_USER_ID_2)) - assertThat(unblockUserAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2))) + } + } + } + + @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( matrixClient: FakeMatrixClient = FakeMatrixClient(), - ) = BlockedUsersPresenter(matrixClient) + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), + ) = BlockedUsersPresenter( + matrixClient = matrixClient, + featureFlagService = featureFlagService, + ) } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 98e3818ad3..1a0602fd15 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -81,5 +81,12 @@ enum class FeatureFlags( description = "Allow user to search for public rooms in their homeserver", defaultValue = 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, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 2f01633858..d8b485871a 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -41,6 +41,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.Mentions -> true FeatureFlags.MarkAsUnread -> true FeatureFlags.RoomDirectorySearch -> false + FeatureFlags.ShowBlockedUsersDetails -> false } } else { false