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, )