Hide "Ask to join" option if Knock feature is disabled.

This commit is contained in:
Benoit Marty 2025-10-17 17:02:19 +02:00 committed by Benoit Marty
parent 8ffae8d416
commit 7cb4a96fbe
5 changed files with 63 additions and 26 deletions

View file

@ -27,6 +27,8 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runCatchingUpdatingState
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.RoomAlias import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@ -45,6 +47,7 @@ class SecurityAndPrivacyPresenter(
@Assisted private val navigator: SecurityAndPrivacyNavigator, @Assisted private val navigator: SecurityAndPrivacyNavigator,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val room: JoinedRoom, private val room: JoinedRoom,
private val featureFlagService: FeatureFlagService,
) : Presenter<SecurityAndPrivacyState> { ) : Presenter<SecurityAndPrivacyState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -55,6 +58,9 @@ class SecurityAndPrivacyPresenter(
override fun present(): SecurityAndPrivacyState { override fun present(): SecurityAndPrivacyState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val isKnockEnabled by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
}.collectAsState(false)
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
val homeserverName = remember { matrixClient.userIdServerName() } val homeserverName = remember { matrixClient.userIdServerName() }
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
@ -149,6 +155,7 @@ class SecurityAndPrivacyPresenter(
editedSettings = editedSettings, editedSettings = editedSettings,
homeserverName = homeserverName, homeserverName = homeserverName,
showEnableEncryptionConfirmation = showEnableEncryptionConfirmation, showEnableEncryptionConfirmation = showEnableEncryptionConfirmation,
canUserSelectAskToJoinOption = isKnockEnabled,
saveAction = saveAction.value, saveAction = saveAction.value,
permissions = permissions, permissions = permissions,
eventSink = ::handleEvents eventSink = ::handleEvents

View file

@ -19,6 +19,7 @@ data class SecurityAndPrivacyState(
val editedSettings: SecurityAndPrivacySettings, val editedSettings: SecurityAndPrivacySettings,
val homeserverName: String, val homeserverName: String,
val showEnableEncryptionConfirmation: Boolean, val showEnableEncryptionConfirmation: Boolean,
val canUserSelectAskToJoinOption: Boolean,
val saveAction: AsyncAction<Unit>, val saveAction: AsyncAction<Unit>,
private val permissions: SecurityAndPrivacyPermissions, private val permissions: SecurityAndPrivacyPermissions,
val eventSink: (SecurityAndPrivacyEvents) -> Unit val eventSink: (SecurityAndPrivacyEvents) -> Unit

View file

@ -30,7 +30,8 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAn
aSecurityAndPrivacyState( aSecurityAndPrivacyState(
savedSettings = aSecurityAndPrivacySettings( savedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
) ),
canUserSelectAskToJoinOption = false,
), ),
aSecurityAndPrivacyState( aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings( editedSettings = aSecurityAndPrivacySettings(
@ -83,6 +84,7 @@ fun aSecurityAndPrivacyState(
canChangeEncryption = true, canChangeEncryption = true,
canChangeRoomVisibility = true canChangeRoomVisibility = true
), ),
canUserSelectAskToJoinOption: Boolean = true,
eventSink: (SecurityAndPrivacyEvents) -> Unit = {} eventSink: (SecurityAndPrivacyEvents) -> Unit = {}
) = SecurityAndPrivacyState( ) = SecurityAndPrivacyState(
editedSettings = editedSettings, editedSettings = editedSettings,
@ -90,6 +92,7 @@ fun aSecurityAndPrivacyState(
homeserverName = homeserverName, homeserverName = homeserverName,
showEnableEncryptionConfirmation = showEncryptionConfirmation, showEnableEncryptionConfirmation = showEncryptionConfirmation,
saveAction = saveAction, saveAction = saveAction,
canUserSelectAskToJoinOption = canUserSelectAskToJoinOption,
permissions = permissions, permissions = permissions,
eventSink = eventSink eventSink = eventSink
) )

View file

@ -81,6 +81,7 @@ fun SecurityAndPrivacyView(
modifier = Modifier.padding(top = 24.dp), modifier = Modifier.padding(top = 24.dp),
edited = state.editedSettings.roomAccess, edited = state.editedSettings.roomAccess,
saved = state.savedSettings.roomAccess, saved = state.savedSettings.roomAccess,
canUserSelectAskToJoinOption = state.canUserSelectAskToJoinOption,
onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) },
) )
} }
@ -176,6 +177,7 @@ private fun SecurityAndPrivacySection(
private fun RoomAccessSection( private fun RoomAccessSection(
edited: SecurityAndPrivacyRoomAccess, edited: SecurityAndPrivacyRoomAccess,
saved: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess,
canUserSelectAskToJoinOption: Boolean,
onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -189,12 +191,14 @@ private fun RoomAccessSection(
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly),
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
) )
ListItem( if (canUserSelectAskToJoinOption) {
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, ListItem(
supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) },
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) },
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin),
) onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) },
)
}
ListItem( ListItem(
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) }, headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) },
supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) }, supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) },

View file

@ -10,6 +10,9 @@ package io.element.android.features.roomdetails.impl.securityandprivacy
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.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
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.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
@ -38,6 +41,7 @@ class SecurityAndPrivacyPresenterTest {
assertThat(showRoomVisibilitySections).isFalse() assertThat(showRoomVisibilitySections).isFalse()
assertThat(showHistoryVisibilitySection).isFalse() assertThat(showHistoryVisibilitySection).isFalse()
assertThat(showEncryptionSection).isFalse() assertThat(showEncryptionSection).isFalse()
assertThat(canUserSelectAskToJoinOption).isFalse()
} }
with(awaitItem()) { with(awaitItem()) {
assertThat(editedSettings).isEqualTo(savedSettings) assertThat(editedSettings).isEqualTo(savedSettings)
@ -48,6 +52,7 @@ class SecurityAndPrivacyPresenterTest {
assertThat(showRoomVisibilitySections).isFalse() assertThat(showRoomVisibilitySections).isFalse()
assertThat(showHistoryVisibilitySection).isTrue() assertThat(showHistoryVisibilitySection).isTrue()
assertThat(showEncryptionSection).isTrue() assertThat(showEncryptionSection).isTrue()
assertThat(canUserSelectAskToJoinOption).isFalse()
} }
} }
} }
@ -56,14 +61,14 @@ class SecurityAndPrivacyPresenterTest {
fun `present - room info change updates saved and edited settings`() = runTest { fun `present - room info change updates saved and edited settings`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
initialRoomInfo = aRoomInfo( initialRoomInfo = aRoomInfo(
joinRule = JoinRule.Public, joinRule = JoinRule.Public,
historyVisibility = RoomHistoryVisibility.WorldReadable, historyVisibility = RoomHistoryVisibility.WorldReadable,
canonicalAlias = A_ROOM_ALIAS, canonicalAlias = A_ROOM_ALIAS,
)
) )
) )
)
val presenter = createSecurityAndPrivacyPresenter(room = room) val presenter = createSecurityAndPrivacyPresenter(room = room)
presenter.test { presenter.test {
skipItems(1) skipItems(1)
@ -163,10 +168,10 @@ class SecurityAndPrivacyPresenterTest {
fun `present - room visibility loading and change`() = runTest { fun `present - room visibility loading and change`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared)
) )
) )
val presenter = createSecurityAndPrivacyPresenter(room = room) val presenter = createSecurityAndPrivacyPresenter(room = room)
presenter.test { presenter.test {
@ -212,10 +217,10 @@ class SecurityAndPrivacyPresenterTest {
val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) } val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared)
), ),
enableEncryptionResult = enableEncryptionLambda, enableEncryptionResult = enableEncryptionLambda,
updateJoinRuleResult = updateJoinRuleLambda, updateJoinRuleResult = updateJoinRuleLambda,
updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda,
@ -279,10 +284,10 @@ class SecurityAndPrivacyPresenterTest {
val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) } val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private)
), ),
enableEncryptionResult = enableEncryptionLambda, enableEncryptionResult = enableEncryptionLambda,
updateJoinRuleResult = updateJoinRuleLambda, updateJoinRuleResult = updateJoinRuleLambda,
updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda,
@ -335,6 +340,21 @@ class SecurityAndPrivacyPresenterTest {
} }
} }
@Test
fun `present - canUserSelectAskToJoinOption is true if the Knock feature flag is enabled`() = runTest {
val presenter = createSecurityAndPrivacyPresenter(
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.Knock.key to true,
)
)
)
presenter.test {
assertThat(awaitItem().canUserSelectAskToJoinOption).isFalse()
assertThat(awaitItem().canUserSelectAskToJoinOption).isTrue()
}
}
private fun createSecurityAndPrivacyPresenter( private fun createSecurityAndPrivacyPresenter(
serverName: String = "matrix.org", serverName: String = "matrix.org",
room: FakeJoinedRoom = FakeJoinedRoom( room: FakeJoinedRoom = FakeJoinedRoom(
@ -345,13 +365,15 @@ class SecurityAndPrivacyPresenterTest {
), ),
), ),
navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
): SecurityAndPrivacyPresenter { ): SecurityAndPrivacyPresenter {
return SecurityAndPrivacyPresenter( return SecurityAndPrivacyPresenter(
room = room, room = room,
matrixClient = FakeMatrixClient( matrixClient = FakeMatrixClient(
userIdServerNameLambda = { serverName }, userIdServerNameLambda = { serverName },
), ),
navigator = navigator navigator = navigator,
featureFlagService = featureFlagService,
) )
} }
} }