From 01681e9fff324d0be4c2d7139ebe622a5e779cdf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Sep 2026 16:14:49 +0200 Subject: [PATCH] Move the "Blocked users" item to the Moderation and safety screen. The item is removed from the account section of the root settings screen and added at the bottom of the Moderation and safety screen. As before, it is only shown when at least one user is blocked, with the number of blocked users. ModerationAndSafetyPresenter now computes the number of blocked users, and ModerationAndSafetyNode exposes a Callback to navigate to the blocked users screen. Tests are moved accordingly. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../preferences/impl/PreferencesFlowNode.kt | 11 +++--- .../account/PreferencesAccountCallback.kt | 1 - .../account/PreferencesAccountPresenter.kt | 9 ----- .../impl/account/PreferencesAccountState.kt | 5 +-- .../PreferencesAccountStatePreviewParam.kt | 4 --- .../impl/account/PreferencesAccountView.kt | 13 ------- .../moderation/ModerationAndSafetyNode.kt | 8 +++++ .../ModerationAndSafetyPresenter.kt | 12 +++++++ .../moderation/ModerationAndSafetyState.kt | 5 ++- .../ModerationAndSafetyStatePreviewParam.kt | 3 ++ .../moderation/ModerationAndSafetyView.kt | 29 ++++++++++++++- .../impl/root/PreferencesRootNode.kt | 1 - .../impl/root/PreferencesRootView.kt | 3 -- .../impl/src/main/res/values/localazy.xml | 1 + .../PreferencesAccountPresenterTest.kt | 21 ----------- .../account/PreferencesAccountViewTest.kt | 19 ---------- .../ModerationAndSafetyPresenterTest.kt | 23 ++++++++++++ .../moderation/ModerationAndSafetyViewTest.kt | 36 +++++++++++++++++++ .../impl/root/PreferencesRootViewTest.kt | 2 -- 19 files changed, 122 insertions(+), 84 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 4d6c2b772d..5d8bb2bf42 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -193,10 +193,6 @@ class PreferencesFlowNode( backstack.push(NavTarget.UserProfile(matrixUser)) } - override fun navigateToBlockedUsers() { - backstack.push(NavTarget.BlockedUsers) - } - override fun startSignOutFlow() { backstack.push(NavTarget.SignOut) } @@ -236,7 +232,12 @@ class PreferencesFlowNode( createNode(buildContext, listOf(callback)) } NavTarget.ModerationAndSafety -> { - createNode(buildContext) + val callback = object : ModerationAndSafetyNode.Callback { + override fun navigateToBlockedUsers() { + backstack.push(NavTarget.BlockedUsers) + } + } + createNode(buildContext, listOf(callback)) } NavTarget.LocationSettings -> { createNode(buildContext) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountCallback.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountCallback.kt index 2d770898d8..90504aab2c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountCallback.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountCallback.kt @@ -16,7 +16,6 @@ interface PreferencesAccountCallback { fun navigateToNotificationSettings() fun navigateToLinkNewDevice() fun navigateToUserProfile(matrixUser: MatrixUser) - fun navigateToBlockedUsers() fun startSignOutFlow() fun startAccountDeactivationFlow() } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenter.kt index f5a46dbf50..ad9686be9c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenter.kt @@ -27,8 +27,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach @Inject class PreferencesAccountPresenter( @@ -67,12 +65,6 @@ class PreferencesAccountPresenter( canDeactivateAccount = matrixClient.canDeactivateAccount() } - val numberOfBlockedUsers by produceState(initialValue = 0) { - matrixClient.ignoredUsersFlow - .onEach { value = it.size } - .launchIn(this) - } - val directLogoutState = directLogoutPresenter.present() val accountManagementUrl by produceState(initialValue = null) { @@ -91,7 +83,6 @@ class PreferencesAccountPresenter( canReportBug = canReportBug, showLinkNewDevice = showLinkNewDevice, canDeactivateAccount = canDeactivateAccount, - numberOfBlockedUsers = numberOfBlockedUsers, directLogoutState = directLogoutState, snackbarMessage = snackbarMessage, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountState.kt index d462d22e40..00abbf2ad4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountState.kt @@ -19,9 +19,6 @@ data class PreferencesAccountState( val canReportBug: Boolean, val showLinkNewDevice: Boolean, val canDeactivateAccount: Boolean, - val numberOfBlockedUsers: Int, val directLogoutState: DirectLogoutState, val snackbarMessage: SnackbarMessage?, -) { - val showBlockedUsersItem = numberOfBlockedUsers > 0 -} +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountStatePreviewParam.kt index fe26cf7e76..0cf42104ef 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountStatePreviewParam.kt @@ -26,7 +26,6 @@ open class PreferencesAccountStatePreviewParam : PreviewParameterProvider Unit, onModerationAndSafetyClick: () -> Unit, onOpenNotificationSettings: () -> Unit, - onOpenBlockedUsers: () -> Unit, onSignOutClick: () -> Unit, onDeactivateClick: () -> Unit, modifier: Modifier = Modifier, @@ -46,7 +45,6 @@ fun PreferencesAccountView( state = state, onManageAccountClick = onManageAccountClick, onLinkNewDeviceClick = onLinkNewDeviceClick, - onOpenBlockedUsers = onOpenBlockedUsers, ) OtherSettingsSection( state = state, @@ -69,7 +67,6 @@ private fun ManageAccountSection( state: PreferencesAccountState, onManageAccountClick: (url: String) -> Unit, onLinkNewDeviceClick: () -> Unit, - onOpenBlockedUsers: () -> Unit, ) { PreferenceCategory( title = stringResource(CommonStrings.common_account_settings), @@ -90,14 +87,6 @@ private fun ManageAccountSection( onClick = onLinkNewDeviceClick, ) } - if (state.showBlockedUsersItem) { - ListItem( - content = { Text(stringResource(id = CommonStrings.common_blocked_users)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), - onClick = onOpenBlockedUsers, - trailingContent = ListItemContent.Text(state.numberOfBlockedUsers.toString()), - ) - } } } @@ -173,9 +162,7 @@ internal fun PreferencesAccountViewPreview(@PreviewParameter(PreferencesAccountS onManageAccountClick = {}, onLinkNewDeviceClick = {}, onOpenNotificationSettings = {}, - onOpenBlockedUsers = {}, onSignOutClick = {}, onDeactivateClick = {}, ) } - diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyNode.kt index 2c835e9a9c..9ea6fe4bf5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyNode.kt @@ -15,6 +15,7 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -24,6 +25,12 @@ class ModerationAndSafetyNode( @Assisted plugins: List, private val presenter: ModerationAndSafetyPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun navigateToBlockedUsers() + } + + private val callback: Callback = callback() + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -31,6 +38,7 @@ class ModerationAndSafetyNode( state = state, modifier = modifier, onBackClick = ::navigateUp, + onOpenBlockedUsers = callback::navigateToBlockedUsers, ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenter.kt index 0ce558aca9..dbfbce2fdf 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenter.kt @@ -10,16 +10,21 @@ package io.element.android.features.preferences.impl.moderation import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @Inject class ModerationAndSafetyPresenter( + private val matrixClient: MatrixClient, private val sessionPreferencesStore: SessionPreferencesStore, private val mediaPreviewConfigStateStore: MediaPreviewConfigStateStore, @SessionCoroutineScope @@ -33,6 +38,12 @@ class ModerationAndSafetyPresenter( val mediaPreviewConfigState = mediaPreviewConfigStateStore.state() + val numberOfBlockedUsers by produceState(initialValue = 0) { + matrixClient.ignoredUsersFlow + .onEach { value = it.size } + .launchIn(this) + } + fun handleEvent(event: ModerationAndSafetyEvent) { when (event) { is ModerationAndSafetyEvent.SetSharePresenceEnabled -> sessionCoroutineScope.launch { @@ -46,6 +57,7 @@ class ModerationAndSafetyPresenter( return ModerationAndSafetyState( isSharePresenceEnabled = isSharePresenceEnabled, mediaPreviewConfigState = mediaPreviewConfigState, + numberOfBlockedUsers = numberOfBlockedUsers, eventSink = ::handleEvent, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyState.kt index ac08054c77..4ff0513294 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyState.kt @@ -10,5 +10,8 @@ package io.element.android.features.preferences.impl.moderation data class ModerationAndSafetyState( val isSharePresenceEnabled: Boolean, val mediaPreviewConfigState: MediaPreviewConfigState, + val numberOfBlockedUsers: Int, val eventSink: (ModerationAndSafetyEvent) -> Unit -) +) { + val showBlockedUsersItem = numberOfBlockedUsers > 0 +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyStatePreviewParam.kt index 3562bcfba2..27612216d3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyStatePreviewParam.kt @@ -20,6 +20,7 @@ open class ModerationAndSafetyStatePreviewParam : PreviewParameterProvider = AsyncAction.Uninitialized, setHideInviteAvatarsAction: AsyncAction = AsyncAction.Uninitialized, + numberOfBlockedUsers: Int = 0, eventSink: (ModerationAndSafetyEvent) -> Unit = {}, ) = ModerationAndSafetyState( isSharePresenceEnabled = isSharePresenceEnabled, @@ -38,5 +40,6 @@ fun aModerationAndSafetyState( setTimelineMediaPreviewAction = setTimelineMediaPreviewAction, setHideInviteAvatarsAction = setHideInviteAvatarsAction ), + numberOfBlockedUsers = numberOfBlockedUsers, eventSink = eventSink ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyView.kt index 24c5598149..3322f28413 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyView.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.list.ListItemContent @@ -24,6 +25,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewBlack import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight +import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListSectionHeader import io.element.android.libraries.designsystem.theme.components.ListSupportingText @@ -43,6 +45,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun ModerationAndSafetyView( state: ModerationAndSafetyState, onBackClick: () -> Unit, + onOpenBlockedUsers: () -> Unit, modifier: Modifier = Modifier, ) { val snackbarDispatcher = LocalSnackbarDispatcher.current @@ -63,6 +66,29 @@ fun ModerationAndSafetyView( PresenceSection(state) InvitesSection(state) MediaSection(state) + if (state.showBlockedUsersItem) { + BlockedUsersSection( + numberOfBlockedUsers = state.numberOfBlockedUsers, + onOpenBlockedUsers = onOpenBlockedUsers, + ) + } + } +} + +@Composable +private fun BlockedUsersSection( + numberOfBlockedUsers: Int, + onOpenBlockedUsers: () -> Unit, +) { + PreferenceCategory( + title = stringResource(R.string.screen_moderation_and_safety_other_users_heading), + ) { + ListItem( + content = { Text(stringResource(id = CommonStrings.common_blocked_users)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), + onClick = onOpenBlockedUsers, + trailingContent = ListItemContent.Text(numberOfBlockedUsers.toString()), + ) } } @@ -175,6 +201,7 @@ internal fun ModerationAndSafetyViewBlackPreview(@PreviewParameter(ModerationAnd private fun ContentToPreview(state: ModerationAndSafetyState) { ModerationAndSafetyView( state = state, - onBackClick = { } + onBackClick = { }, + onOpenBlockedUsers = { }, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index a498cc40f2..57ed6294fa 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -89,7 +89,6 @@ class PreferencesRootNode( onManageAccountClick = { onManageAccountClick(activity, it, isDark) }, onOpenNotificationSettings = callback::navigateToNotificationSettings, onEditProfileClick = callback::navigateToUserProfile, - onOpenBlockedUsers = callback::navigateToBlockedUsers, onSignOutClick = { if (state.preferencesAccountState.directLogoutState.canDoDirectSignOut) { state.preferencesAccountState.directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false)) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 8ffb6bb307..1449ece700 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -93,7 +93,6 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onModerationAndSafetyClick: () -> Unit, onOpenNotificationSettings: () -> Unit, - onOpenBlockedUsers: () -> Unit, onSignOutClick: () -> Unit, onDeactivateClick: () -> Unit, modifier: Modifier = Modifier, @@ -140,7 +139,6 @@ fun PreferencesRootView( onOpenRageShake = onOpenRageShake, onModerationAndSafetyClick = onModerationAndSafetyClick, onOpenNotificationSettings = onOpenNotificationSettings, - onOpenBlockedUsers = onOpenBlockedUsers, onSignOutClick = onSignOutClick, onDeactivateClick = onDeactivateClick, ) @@ -441,7 +439,6 @@ private fun ContentToPreview(state: PreferencesRootState) { onOpenRageShake = {}, onModerationAndSafetyClick = {}, onOpenNotificationSettings = {}, - onOpenBlockedUsers = {}, onSignOutClick = {}, onDeactivateClick = {}, ) diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index f1d8baf603..866cdae584 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -62,6 +62,7 @@ "Feeling experimental?" "Labs" "Invites" + "Other users" "Your appearance" "Additional settings" "Call ringtone" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenterTest.kt index 89f01b465c..cd1bd186f4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountPresenterTest.kt @@ -22,8 +22,6 @@ import io.element.android.libraries.matrix.api.oauth.AccountManagementAction import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_SESSION_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_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -32,8 +30,6 @@ import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test -import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -69,8 +65,6 @@ class PreferencesAccountPresenterTest { assertThat(initialState.showSecureBackupBadge).isFalse() assertThat(initialState.accountManagementUrl).isNull() assertThat(initialState.showLinkNewDevice).isFalse() - assertThat(initialState.numberOfBlockedUsers).isEqualTo(0) - assertThat(initialState.showBlockedUsersItem).isFalse() assertThat(initialState.directLogoutState).isEqualTo(aDirectLogoutState()) assertThat(initialState.snackbarMessage).isNull() val loadedState = consumeItemsUntilPredicate { it.accountManagementUrl != null }.last() @@ -98,21 +92,6 @@ class PreferencesAccountPresenterTest { } } - @Test - fun `present - number of blocked users`() = runTest { - createPreferencesAccountPresenter( - matrixClient = FakeMatrixClient( - canDeactivateAccountResult = { true }, - accountManagementUrlResult = { Result.success("") }, - ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2)), - ), - ).test { - val state = consumeItemsUntilPredicate { it.numberOfBlockedUsers == 2 }.last() - assertThat(state.showBlockedUsersItem).isTrue() - cancelAndIgnoreRemainingEvents() - } - } - @Test fun `present - secure backup badge`() = runTest { val indicatorService = FakeIndicatorService() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountViewTest.kt index a209097ad7..f3ac4d1898 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/account/PreferencesAccountViewTest.kt @@ -62,23 +62,6 @@ class PreferencesAccountViewTest : RobolectricTest() { onNodeWithText(activity!!.getString(CommonStrings.common_link_new_device)).assertDoesNotExist() } - @Test - fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest { - ensureCalledOnce { callback -> - setView( - aPreferencesAccountState(numberOfBlockedUsers = 1), - onOpenBlockedUsers = callback, - ) - clickOn(CommonStrings.common_blocked_users) - } - } - - @Test - fun `when numberOfBlockedUsers is 0, item is not shown`() = runAndroidComposeUiTest { - setView(aPreferencesAccountState(numberOfBlockedUsers = 0)) - onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist() - } - @Test fun `click on Notification invokes the expected callback`() = runAndroidComposeUiTest { ensureCalledOnce { callback -> @@ -177,7 +160,6 @@ private fun AndroidComposeUiTest.setView( onOpenRageShake: () -> Unit = EnsureNeverCalled(), onModerationAndSafetyClick: () -> Unit = EnsureNeverCalled(), onOpenNotificationSettings: () -> Unit = EnsureNeverCalled(), - onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(), onSignOutClick: () -> Unit = EnsureNeverCalled(), onDeactivateClick: () -> Unit = EnsureNeverCalled(), ) { @@ -190,7 +172,6 @@ private fun AndroidComposeUiTest.setView( onOpenRageShake = onOpenRageShake, onModerationAndSafetyClick = onModerationAndSafetyClick, onOpenNotificationSettings = onOpenNotificationSettings, - onOpenBlockedUsers = onOpenBlockedUsers, onSignOutClick = onSignOutClick, onDeactivateClick = onDeactivateClick, ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenterTest.kt index dec3803338..0517a1b601 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyPresenterTest.kt @@ -10,10 +10,16 @@ package io.element.android.features.preferences.impl.moderation import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.media.MediaPreviewValue +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 import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.test +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -31,10 +37,25 @@ class ModerationAndSafetyPresenterTest { assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On) assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Uninitialized) assertThat(mediaPreviewConfigState.setTimelineMediaPreviewAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(numberOfBlockedUsers).isEqualTo(0) + assertThat(showBlockedUsersItem).isFalse() } } } + @Test + fun `present - number of blocked users`() = runTest { + createModerationAndSafetyPresenter( + matrixClient = FakeMatrixClient( + ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2)), + ), + ).test { + val state = consumeItemsUntilPredicate { it.numberOfBlockedUsers == 2 }.last() + assertThat(state.showBlockedUsersItem).isTrue() + cancelAndIgnoreRemainingEvents() + } + } + @Test fun `present - share presence off on`() = runTest { createModerationAndSafetyPresenter().test { @@ -121,9 +142,11 @@ class ModerationAndSafetyPresenterTest { } private fun CoroutineScope.createModerationAndSafetyPresenter( + matrixClient: FakeMatrixClient = FakeMatrixClient(), sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(), mediaPreviewConfigStateStore: MediaPreviewConfigStateStore = FakeMediaPreviewConfigStateStore(), ) = ModerationAndSafetyPresenter( + matrixClient = matrixClient, sessionPreferencesStore = sessionPreferencesStore, mediaPreviewConfigStateStore = mediaPreviewConfigStateStore, sessionCoroutineScope = this, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyViewTest.kt index 3589f24154..6d72486846 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/moderation/ModerationAndSafetyViewTest.kt @@ -12,10 +12,16 @@ package io.element.android.features.preferences.impl.moderation import androidx.activity.ComponentActivity import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasClickAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.v2.runAndroidComposeUiTest import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.media.MediaPreviewValue +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn @@ -138,16 +144,46 @@ class ModerationAndSafetyViewTest : RobolectricTest() { clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) } + + @Test + fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + setModerationAndSafetyView( + state = aModerationAndSafetyState( + numberOfBlockedUsers = 1, + eventSink = eventsRecorder, + ), + onOpenBlockedUsers = callback, + ) + val text = activity!!.getString(CommonStrings.common_blocked_users) + onNode(hasText(text) and hasClickAction()).performScrollTo().performClick() + } + } + + @Test + fun `when numberOfBlockedUsers is 0, Blocked users item is not shown`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder(expectEvents = false) + setModerationAndSafetyView( + state = aModerationAndSafetyState( + numberOfBlockedUsers = 0, + eventSink = eventsRecorder, + ), + ) + onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist() + } } private fun AndroidComposeUiTest.setModerationAndSafetyView( state: ModerationAndSafetyState, onBackClick: () -> Unit = EnsureNeverCalled(), + onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(), ) { setContent { ModerationAndSafetyView( state = state, onBackClick = onBackClick, + onOpenBlockedUsers = onOpenBlockedUsers, ) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt index 9c5b181833..3afd6cde9d 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootViewTest.kt @@ -409,7 +409,6 @@ private fun AndroidComposeUiTest.setView( onOpenRageShake: () -> Unit = EnsureNeverCalled(), onModerationAndSafetyClick: () -> Unit = EnsureNeverCalled(), onOpenNotificationSettings: () -> Unit = EnsureNeverCalled(), - onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(), onSignOutClick: () -> Unit = EnsureNeverCalled(), onDeactivateClick: () -> Unit = EnsureNeverCalled(), ) { @@ -433,7 +432,6 @@ private fun AndroidComposeUiTest.setView( onOpenRageShake = onOpenRageShake, onModerationAndSafetyClick = onModerationAndSafetyClick, onOpenNotificationSettings = onOpenNotificationSettings, - onOpenBlockedUsers = onOpenBlockedUsers, onSignOutClick = onSignOutClick, onDeactivateClick = onDeactivateClick, )