diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 4408deab1e..ccb4c48e7e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -164,7 +164,7 @@ class RoomDetailsPresenter( val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value) val canShowSecurityAndPrivacy by remember { derivedStateOf { - isKnockRequestsEnabled && roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny + roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt index abc0ff72af..4ff45bb588 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt @@ -27,6 +27,8 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState 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.RoomAlias import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -45,6 +47,7 @@ class SecurityAndPrivacyPresenter( @Assisted private val navigator: SecurityAndPrivacyNavigator, private val matrixClient: MatrixClient, private val room: JoinedRoom, + private val featureFlagService: FeatureFlagService, ) : Presenter { @AssistedFactory interface Factory { @@ -55,6 +58,9 @@ class SecurityAndPrivacyPresenter( override fun present(): SecurityAndPrivacyState { val coroutineScope = rememberCoroutineScope() + val isKnockEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(false) val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val homeserverName = remember { matrixClient.userIdServerName() } val syncUpdateFlow = room.syncUpdateFlow.collectAsState() @@ -149,6 +155,7 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings, homeserverName = homeserverName, showEnableEncryptionConfirmation = showEnableEncryptionConfirmation, + isKnockEnabled = isKnockEnabled, saveAction = saveAction.value, permissions = permissions, eventSink = ::handleEvents diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt index eb22ec2597..dbc0125abb 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt @@ -19,6 +19,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val showEnableEncryptionConfirmation: Boolean, + val isKnockEnabled: Boolean, val saveAction: AsyncAction, private val permissions: SecurityAndPrivacyPermissions, val eventSink: (SecurityAndPrivacyEvents) -> Unit diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt index 00001ff69d..8964ee59c5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt @@ -30,7 +30,8 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -90,6 +98,7 @@ fun aSecurityAndPrivacyState( homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, + isKnockEnabled = isKnockEnabled, permissions = permissions, eventSink = eventSink ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt index c46f8a7a6f..7428763dd8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt @@ -81,6 +81,7 @@ fun SecurityAndPrivacyView( modifier = Modifier.padding(top = 24.dp), edited = state.editedSettings.roomAccess, saved = state.savedSettings.roomAccess, + isKnockEnabled = state.isKnockEnabled, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, ) } @@ -176,6 +177,7 @@ private fun SecurityAndPrivacySection( private fun RoomAccessSection( edited: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess, + isKnockEnabled: Boolean, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, modifier: Modifier = Modifier, ) { @@ -189,12 +191,18 @@ private fun RoomAccessSection( trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, ) - ListItem( - headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, - supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, - trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), - onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, - ) + // Show Ask to join option in two cases: + // - the Knock FF is enabled + // - AskToJoin is the current saved value + if (saved == SecurityAndPrivacyRoomAccess.AskToJoin || isKnockEnabled) { + ListItem( + headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, + supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, + trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), + onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, + enabled = isKnockEnabled, + ) + } ListItem( 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)) }, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 695168aa16..27adc6cb44 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -719,10 +719,6 @@ class RoomDetailsPresenterTest { val presenter = createRoomDetailsPresenter(room = room, featureFlagService = featureFlagService) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { skipItems(1) - with(awaitItem()) { - assertThat(canShowSecurityAndPrivacy).isFalse() - } - featureFlagService.setFeatureEnabled(FeatureFlags.Knock, true) with(awaitItem()) { assertThat(canShowSecurityAndPrivacy).isTrue() } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt index bc2a3f62f7..10df56e639 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt @@ -10,6 +10,9 @@ package io.element.android.features.roomdetails.impl.securityandprivacy import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction 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.join.JoinRule import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility @@ -38,6 +41,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isFalse() assertThat(showEncryptionSection).isFalse() + assertThat(isKnockEnabled).isFalse() } with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) @@ -48,6 +52,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isTrue() assertThat(showEncryptionSection).isTrue() + assertThat(isKnockEnabled).isFalse() } } } @@ -56,14 +61,14 @@ class SecurityAndPrivacyPresenterTest { fun `present - room info change updates saved and edited settings`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - initialRoomInfo = aRoomInfo( - joinRule = JoinRule.Public, - historyVisibility = RoomHistoryVisibility.WorldReadable, - canonicalAlias = A_ROOM_ALIAS, + canSendStateResult = { _, _ -> Result.success(true) }, + initialRoomInfo = aRoomInfo( + joinRule = JoinRule.Public, + historyVisibility = RoomHistoryVisibility.WorldReadable, + canonicalAlias = A_ROOM_ALIAS, + ) ) ) - ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { skipItems(1) @@ -163,10 +168,10 @@ class SecurityAndPrivacyPresenterTest { fun `present - room visibility loading and change`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) - ) + canSendStateResult = { _, _ -> Result.success(true) }, + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) + ) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { @@ -212,10 +217,10 @@ class SecurityAndPrivacyPresenterTest { val updateRoomHistoryVisibilityLambda = lambdaRecorder> { Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) - ), + canSendStateResult = { _, _ -> Result.success(true) }, + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) + ), enableEncryptionResult = enableEncryptionLambda, updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, @@ -279,10 +284,10 @@ class SecurityAndPrivacyPresenterTest { val updateRoomHistoryVisibilityLambda = lambdaRecorder> { Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) - ), + canSendStateResult = { _, _ -> Result.success(true) }, + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) + ), enableEncryptionResult = enableEncryptionLambda, updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, @@ -323,7 +328,8 @@ class SecurityAndPrivacyPresenterTest { ) // Saved settings are updated 2 times to match the edited settings skipItems(3) - with(awaitItem()) { + val state = awaitItem() + with(state) { assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(savedSettings.isVisibleInRoomDirectory).isNotEqualTo(editedSettings.isVisibleInRoomDirectory) assertThat(canBeSaved).isTrue() @@ -332,6 +338,26 @@ class SecurityAndPrivacyPresenterTest { assert(updateJoinRuleLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() + // Clear error + state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + } + } + } + + @Test + fun `present - isKnockEnabled 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().isKnockEnabled).isFalse() + assertThat(awaitItem().isKnockEnabled).isTrue() } } @@ -345,13 +371,15 @@ class SecurityAndPrivacyPresenterTest { ), ), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( room = room, matrixClient = FakeMatrixClient( userIdServerNameLambda = { serverName }, ), - navigator = navigator + navigator = navigator, + featureFlagService = featureFlagService, ) } } diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en.png index 8b30aa7bf4..ee9db33a17 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d627cd374fe0746d2fc84fffecf6a1636fb68784ed5edae3b830fe4ee2b3751a -size 61398 +oid sha256:9807bf17918c39f2e6e4cddf87237fa26f63732f48210b0f803114bcf4c98451 +size 61360 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_9_en.png new file mode 100644 index 0000000000..2456c5ce5e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ad13a47522e6365928e7209e515a47a7e4fafc28ad87361dffdf92c95b3ff06 +size 62563 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en.png index bf00730127..d1d6c2f15e 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70979d3b81a521b6208da2f3a95187bc7b3ae0ea52e3dd35e2bbd91df14c007f -size 63318 +oid sha256:ade5d824f6f466960931abcbc86cb9a286cbf6bd9a3adf875f446dc094e291d5 +size 63393 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_9_en.png new file mode 100644 index 0000000000..d646d3d526 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2cfda5b044d11e4e75fa83c9bad7b7f03a328422881cbededfaee8ab091d1db +size 64586