From 8ffae8d41630d630951ccd19db717e74eb29d973 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Oct 2025 16:56:57 +0200 Subject: [PATCH 01/17] Remove Knock FF check to show the Security and Privacy entry point. --- .../android/features/roomdetails/impl/RoomDetailsPresenter.kt | 2 +- .../features/roomdetails/impl/RoomDetailsPresenterTest.kt | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) 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/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() } From 7cb4a96fbeb8d875edc4409783df83db19b369dd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Oct 2025 17:02:19 +0200 Subject: [PATCH 02/17] Hide "Ask to join" option if Knock feature is disabled. --- .../SecurityAndPrivacyPresenter.kt | 7 +++ .../SecurityAndPrivacyState.kt | 1 + .../SecurityAndPrivacyStateProvider.kt | 5 +- .../SecurityAndPrivacyView.kt | 16 +++-- .../SecurityAndPrivacyPresenterTest.kt | 60 +++++++++++++------ 5 files changed, 63 insertions(+), 26 deletions(-) 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..7a98fbf98c 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, + canUserSelectAskToJoinOption = 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..5345e6b43b 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 canUserSelectAskToJoinOption: 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..b7d52d4c66 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 +92,7 @@ fun aSecurityAndPrivacyState( homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, + canUserSelectAskToJoinOption = canUserSelectAskToJoinOption, 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..190ba4da2a 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, + canUserSelectAskToJoinOption = state.canUserSelectAskToJoinOption, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, ) } @@ -176,6 +177,7 @@ private fun SecurityAndPrivacySection( private fun RoomAccessSection( edited: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess, + canUserSelectAskToJoinOption: Boolean, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, modifier: Modifier = Modifier, ) { @@ -189,12 +191,14 @@ 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) }, - ) + if (canUserSelectAskToJoinOption) { + 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) }, + ) + } 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/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt index bc2a3f62f7..842103f210 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(canUserSelectAskToJoinOption).isFalse() } with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) @@ -48,6 +52,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isTrue() assertThat(showEncryptionSection).isTrue() + assertThat(canUserSelectAskToJoinOption).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, @@ -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( serverName: String = "matrix.org", room: FakeJoinedRoom = FakeJoinedRoom( @@ -345,13 +365,15 @@ class SecurityAndPrivacyPresenterTest { ), ), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( room = room, matrixClient = FakeMatrixClient( userIdServerNameLambda = { serverName }, ), - navigator = navigator + navigator = navigator, + featureFlagService = featureFlagService, ) } } From 9d537589ff5b4ba248c3f07ced1291609b140f9a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Oct 2025 17:26:19 +0200 Subject: [PATCH 03/17] Show AskToJoin option if this is the current value, even if the Knock FF is disabled. --- .../SecurityAndPrivacyPresenter.kt | 2 +- .../SecurityAndPrivacyState.kt | 2 +- .../SecurityAndPrivacyStateProvider.kt | 6 ++-- .../SecurityAndPrivacyView.kt | 6 ++-- .../SecurityAndPrivacyPresenterTest.kt | 32 ++++++++++++++++--- 5 files changed, 35 insertions(+), 13 deletions(-) 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 7a98fbf98c..995e07ad90 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 @@ -155,7 +155,7 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings, homeserverName = homeserverName, showEnableEncryptionConfirmation = showEnableEncryptionConfirmation, - canUserSelectAskToJoinOption = isKnockEnabled, + showAskToJoinOption = isKnockEnabled || savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.AskToJoin, 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 5345e6b43b..d0b7e45875 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,7 +19,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val showEnableEncryptionConfirmation: Boolean, - val canUserSelectAskToJoinOption: Boolean, + val showAskToJoinOption: 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 b7d52d4c66..7b73aed43e 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 @@ -31,7 +31,7 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -92,7 +92,7 @@ fun aSecurityAndPrivacyState( homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, - canUserSelectAskToJoinOption = canUserSelectAskToJoinOption, + showAskToJoinOption = showAskToJoinOption, 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 190ba4da2a..00222158ab 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,7 +81,7 @@ fun SecurityAndPrivacyView( modifier = Modifier.padding(top = 24.dp), edited = state.editedSettings.roomAccess, saved = state.savedSettings.roomAccess, - canUserSelectAskToJoinOption = state.canUserSelectAskToJoinOption, + showAskToJoinOption = state.showAskToJoinOption, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, ) } @@ -177,7 +177,7 @@ private fun SecurityAndPrivacySection( private fun RoomAccessSection( edited: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess, - canUserSelectAskToJoinOption: Boolean, + showAskToJoinOption: Boolean, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, modifier: Modifier = Modifier, ) { @@ -191,7 +191,7 @@ private fun RoomAccessSection( trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, ) - if (canUserSelectAskToJoinOption) { + if (showAskToJoinOption) { 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)) }, 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 842103f210..0f950fe7be 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 @@ -41,7 +41,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isFalse() assertThat(showEncryptionSection).isFalse() - assertThat(canUserSelectAskToJoinOption).isFalse() + assertThat(showAskToJoinOption).isFalse() } with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) @@ -52,7 +52,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isTrue() assertThat(showEncryptionSection).isTrue() - assertThat(canUserSelectAskToJoinOption).isFalse() + assertThat(showAskToJoinOption).isFalse() } } } @@ -341,7 +341,7 @@ class SecurityAndPrivacyPresenterTest { } @Test - fun `present - canUserSelectAskToJoinOption is true if the Knock feature flag is enabled`() = runTest { + fun `present - showAskToJoinOption is true if the Knock feature flag is enabled`() = runTest { val presenter = createSecurityAndPrivacyPresenter( featureFlagService = FakeFeatureFlagService( initialState = mapOf( @@ -350,8 +350,30 @@ class SecurityAndPrivacyPresenterTest { ) ) presenter.test { - assertThat(awaitItem().canUserSelectAskToJoinOption).isFalse() - assertThat(awaitItem().canUserSelectAskToJoinOption).isTrue() + assertThat(awaitItem().showAskToJoinOption).isFalse() + assertThat(awaitItem().showAskToJoinOption).isTrue() + } + } + + @Test + fun `present - showAskToJoinOption is true if ask to join is the current value Knock feature flag is enabled`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + featureFlagService = FakeFeatureFlagService( + initialState = mapOf( + FeatureFlags.Knock.key to false, + ) + ), + room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + canSendStateResult = { _, _ -> Result.success(true) }, + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Knock) + ), + ) + ) + presenter.test { + assertThat(awaitItem().showAskToJoinOption).isTrue() + cancelAndIgnoreRemainingEvents() } } From 0811f5e1350c6de9c6aa191e9f7eaf075f3f8911 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 20 Oct 2025 15:46:20 +0000 Subject: [PATCH 04/17] Update screenshots --- ...mpl.securityandprivacy_SecurityAndPrivacyViewDark_3_en.png | 4 ++-- ...pl.securityandprivacy_SecurityAndPrivacyViewLight_3_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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_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 From e9969fda56696d06a3d72b1b43e94db4a703afa9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Oct 2025 14:31:29 +0200 Subject: [PATCH 05/17] Iterate on rendering the "Ask to join" option. --- .../SecurityAndPrivacyPresenter.kt | 2 +- .../SecurityAndPrivacyState.kt | 2 +- .../SecurityAndPrivacyStateProvider.kt | 12 +++++-- .../SecurityAndPrivacyView.kt | 10 ++++-- .../SecurityAndPrivacyPresenterTest.kt | 32 +++---------------- 5 files changed, 23 insertions(+), 35 deletions(-) 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 995e07ad90..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 @@ -155,7 +155,7 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings, homeserverName = homeserverName, showEnableEncryptionConfirmation = showEnableEncryptionConfirmation, - showAskToJoinOption = isKnockEnabled || savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.AskToJoin, + 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 d0b7e45875..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,7 +19,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val showEnableEncryptionConfirmation: Boolean, - val showAskToJoinOption: 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 7b73aed43e..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 @@ -31,7 +31,7 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -92,7 +98,7 @@ fun aSecurityAndPrivacyState( homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, - showAskToJoinOption = showAskToJoinOption, + 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 00222158ab..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,7 +81,7 @@ fun SecurityAndPrivacyView( modifier = Modifier.padding(top = 24.dp), edited = state.editedSettings.roomAccess, saved = state.savedSettings.roomAccess, - showAskToJoinOption = state.showAskToJoinOption, + isKnockEnabled = state.isKnockEnabled, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, ) } @@ -177,7 +177,7 @@ private fun SecurityAndPrivacySection( private fun RoomAccessSection( edited: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess, - showAskToJoinOption: Boolean, + isKnockEnabled: Boolean, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, modifier: Modifier = Modifier, ) { @@ -191,12 +191,16 @@ private fun RoomAccessSection( trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, ) - if (showAskToJoinOption) { + // 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( 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 0f950fe7be..5d2d9ab906 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 @@ -41,7 +41,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isFalse() assertThat(showEncryptionSection).isFalse() - assertThat(showAskToJoinOption).isFalse() + assertThat(isKnockEnabled).isFalse() } with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) @@ -52,7 +52,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isTrue() assertThat(showEncryptionSection).isTrue() - assertThat(showAskToJoinOption).isFalse() + assertThat(isKnockEnabled).isFalse() } } } @@ -341,7 +341,7 @@ class SecurityAndPrivacyPresenterTest { } @Test - fun `present - showAskToJoinOption is true if the Knock feature flag is enabled`() = runTest { + fun `present - isKnockEnabled is true if the Knock feature flag is enabled`() = runTest { val presenter = createSecurityAndPrivacyPresenter( featureFlagService = FakeFeatureFlagService( initialState = mapOf( @@ -350,30 +350,8 @@ class SecurityAndPrivacyPresenterTest { ) ) presenter.test { - assertThat(awaitItem().showAskToJoinOption).isFalse() - assertThat(awaitItem().showAskToJoinOption).isTrue() - } - } - - @Test - fun `present - showAskToJoinOption is true if ask to join is the current value Knock feature flag is enabled`() = runTest { - val presenter = createSecurityAndPrivacyPresenter( - featureFlagService = FakeFeatureFlagService( - initialState = mapOf( - FeatureFlags.Knock.key to false, - ) - ), - room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Knock) - ), - ) - ) - presenter.test { - assertThat(awaitItem().showAskToJoinOption).isTrue() - cancelAndIgnoreRemainingEvents() + assertThat(awaitItem().isKnockEnabled).isFalse() + assertThat(awaitItem().isKnockEnabled).isTrue() } } From e122ff96ba3363a13fa3b15f29ad7d23dc1198d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Oct 2025 14:34:11 +0200 Subject: [PATCH 06/17] Add missing test. --- .../securityandprivacy/SecurityAndPrivacyPresenterTest.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 5d2d9ab906..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 @@ -328,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() @@ -337,6 +338,11 @@ class SecurityAndPrivacyPresenterTest { assert(updateJoinRuleLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() + // Clear error + state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + } } } From d621616801aaaf5ba1805f4c3d1fda2cc05aadaa Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 21 Oct 2025 12:48:19 +0000 Subject: [PATCH 07/17] Update screenshots --- ...impl.securityandprivacy_SecurityAndPrivacyViewDark_9_en.png | 3 +++ ...mpl.securityandprivacy_SecurityAndPrivacyViewLight_9_en.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_9_en.png 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_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 From 9ad9efe547c67572cfd9e5742d28efc4d106cb0a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 14:13:36 +0200 Subject: [PATCH 08/17] Improve API of interface WellknownRetriever to be able to distinguish between 404 and other errors. --- .../DefaultAccountProviderAccessControl.kt | 2 +- .../login/impl/resolver/HomeserverResolver.kt | 2 +- .../WebClientUrlForAuthenticationRetriever.kt | 2 +- ...DefaultAccountProviderAccessControlTest.kt | 9 +- .../changeserver/ChangeServerPresenterTest.kt | 9 +- .../SearchAccountProviderPresenterTest.kt | 21 ++-- .../api/SessionWellknownRetriever.kt | 4 +- .../wellknown/api/WellknownRetriever.kt | 4 +- .../wellknown/api/WellknownRetrieverResult.kt | 31 ++++++ .../impl/DefaultSessionWellknownRetriever.kt | 23 +++- .../impl/DefaultWellknownRetriever.kt | 66 ++++++++---- .../DefaultSessionWellknownRetrieverTest.kt | 101 ++++++++++-------- .../test/FakeSessionWellknownRetriever.kt | 9 +- .../wellknown/test/FakeWellknownRetriever.kt | 9 +- 14 files changed, 196 insertions(+), 96 deletions(-) create mode 100644 libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt index fb739008a7..46256ab5c4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt @@ -41,7 +41,7 @@ class DefaultAccountProviderAccessControl( // Ensure that Element Pro is not required for this account provider val wellKnown = wellknownRetriever.getElementWellKnown( baseUrl = accountProviderUrl.ensureProtocol(), - ) + ).dataOrNull() if (wellKnown?.enforceElementPro == true) { throw AccountProviderAccessException.NeedElementProException( unauthorisedAccountProviderTitle = title, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index 5612a56d5e..7b6f3e4102 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -46,7 +46,7 @@ class HomeserverResolver( wellknownRetriever.getWellKnown(url) } } - val isValid = wellKnown?.isValid().orFalse() + val isValid = wellKnown?.dataOrNull()?.isValid().orFalse() if (isValid) { // Emit the list as soon as possible currentList.add( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt index f72af823f2..fcc8cb0db1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt @@ -30,7 +30,7 @@ class DefaultWebClientUrlForAuthenticationRetriever( Timber.w("Temporary account creation flow is only supported on matrix.org") throw AccountCreationNotSupported() } - val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl) + val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl).dataOrNull() ?: throw AccountCreationNotSupported() val registrationHelperUrl = wellknown.registrationHelperUrl return if (registrationHelperUrl != null) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt index f559c57bf0..c5ace5ca3f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test @@ -155,7 +156,13 @@ class DefaultAccountProviderAccessControlTest { defaultHomeserverListResult = { allowedAccountProviders }, ), wellknownRetriever = FakeWellknownRetriever( - getElementWellKnownResult = { elementWellKnown }, + getElementWellKnownResult = { + if (elementWellKnown == null) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Success(elementWellKnown) + } + }, ), ) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index 620df9bba4..3b88ab03c0 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -114,9 +115,11 @@ class ChangeServerPresenterTest { @Test fun `present - change server element pro required error`() = runTest { - val getElementWellKnownResult = lambdaRecorder { - anElementWellKnown( - enforceElementPro = true, + val getElementWellKnownResult = lambdaRecorder> { + WellknownRetrieverResult.Success( + anElementWellKnown( + enforceElementPro = true, + ) ) } createPresenter( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index b679c92ee1..79cd3c954a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnownBaseConfig +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -94,12 +95,12 @@ class SearchAccountProviderPresenterTest { @Test fun `present - enter text one result with wellknown`() = runTest { - val getWellKnownResult = lambdaRecorder { + val getWellKnownResult = lambdaRecorder> { when (it) { - "https://test.org" -> error("not found") - "https://test.com" -> error("not found") - "https://test.io" -> aWellKnown() - "https://test" -> error("not found") + "https://test.org" -> WellknownRetrieverResult.NotFound + "https://test.com" -> WellknownRetrieverResult.NotFound + "https://test.io" -> WellknownRetrieverResult.Success(aWellKnown()) + "https://test" -> WellknownRetrieverResult.NotFound else -> error("should not happen") } } @@ -138,12 +139,12 @@ class SearchAccountProviderPresenterTest { @Test fun `present - enter text two results with wellknown`() = runTest { - val getWellKnownResult = lambdaRecorder { + val getWellKnownResult = lambdaRecorder> { when (it) { - "https://test.org" -> aWellKnown() - "https://test.com" -> error("not found") - "https://test.io" -> aWellKnown() - "https://test" -> error("not found") + "https://test.org" -> WellknownRetrieverResult.Success(aWellKnown()) + "https://test.com" -> WellknownRetrieverResult.NotFound + "https://test.io" -> WellknownRetrieverResult.Success(aWellKnown()) + "https://test" -> WellknownRetrieverResult.NotFound else -> error("should not happen") } } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt index 7f7b9b983f..1c5570db94 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt @@ -8,6 +8,6 @@ package io.element.android.libraries.wellknown.api interface SessionWellknownRetriever { - suspend fun getWellKnown(): WellKnown? - suspend fun getElementWellKnown(): ElementWellKnown? + suspend fun getWellKnown(): WellknownRetrieverResult + suspend fun getElementWellKnown(): WellknownRetrieverResult } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt index e617bc8e13..4675a0cb18 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt @@ -8,6 +8,6 @@ package io.element.android.libraries.wellknown.api interface WellknownRetriever { - suspend fun getWellKnown(baseUrl: String): WellKnown? - suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? + suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult + suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt new file mode 100644 index 0000000000..a094380b82 --- /dev/null +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.api + +sealed interface WellknownRetrieverResult { + /** + * Well-known data has been successfully retrieved. + */ + data class Success(val data: T) : WellknownRetrieverResult + + /** + * Well-known data is not found (file does not exist server side, we got a 404). + */ + data object NotFound : WellknownRetrieverResult + + /** + * Any other error. + */ + data class Error(val exception: Exception) : WellknownRetrieverResult + + fun dataOrNull(): T? = when (this) { + is Success -> data + is Error -> null + NotFound -> null + } +} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt index 6116435970..1c7e28e62c 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.SessionWellknownRetriever import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import timber.log.Timber @ContributesBinding(SessionScope::class) @@ -26,7 +27,7 @@ class DefaultSessionWellknownRetriever( ) : SessionWellknownRetriever { private val domain by lazy { matrixClient.userIdServerName() } - override suspend fun getWellKnown(): WellKnown? { + override suspend fun getWellKnown(): WellknownRetrieverResult { val url = "https://$domain/.well-known/matrix/client" return matrixClient .getUrl(url) @@ -36,10 +37,17 @@ class DefaultSessionWellknownRetriever( } .onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") } .map { it.map() } - .getOrNull() + .fold( + onSuccess = { + WellknownRetrieverResult.Success(it) + }, + onFailure = { + WellknownRetrieverResult.Error(it as Exception) + } + ) } - override suspend fun getElementWellKnown(): ElementWellKnown? { + override suspend fun getElementWellKnown(): WellknownRetrieverResult { val url = "https://$domain/.well-known/element/element.json" return matrixClient .getUrl(url) @@ -49,6 +57,13 @@ class DefaultSessionWellknownRetriever( } .onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") } .map { it.map() } - .getOrNull() + .fold( + onSuccess = { + WellknownRetrieverResult.Success(it) + }, + onFailure = { + WellknownRetrieverResult.Error(it as Exception) + } + ) } } diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt index aa0e28e85a..c369886459 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt @@ -10,46 +10,72 @@ package io.element.android.libraries.wellknown.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult +import retrofit2.HttpException import timber.log.Timber +import java.net.HttpURLConnection @ContributesBinding(AppScope::class) @Inject class DefaultWellknownRetriever( private val retrofitFactory: RetrofitFactory, ) : WellknownRetriever { - override suspend fun getWellKnown(baseUrl: String): WellKnown? { - val wellknownApi = buildWellknownApi(baseUrl) ?: return null - return try { - wellknownApi.getWellKnown().map() - } catch (e: Exception) { - Timber.e(e, "Failed to retrieve well-known data for $baseUrl") - null - } + override suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult { + return buildWellknownApi(baseUrl) + .map { wellknownApi -> + try { + val result = wellknownApi.getWellKnown().map() + WellknownRetrieverResult.Success(result) + } catch (e: Exception) { + Timber.e(e, "Failed to retrieve well-known data for $baseUrl") + if ((e as? HttpException)?.code() == HttpURLConnection.HTTP_NOT_FOUND) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Error(e) + } + } + } + .fold( + onSuccess = { it }, + onFailure = { WellknownRetrieverResult.Error(it as Exception) } + ) } - override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? { - val wellknownApi = buildWellknownApi(baseUrl) ?: return null - return try { - wellknownApi.getElementWellKnown().map() - } catch (e: Exception) { - Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") - null - } + override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult { + return buildWellknownApi(baseUrl) + .map { wellknownApi -> + try { + val result = wellknownApi.getElementWellKnown().map() + WellknownRetrieverResult.Success(result) + } catch (e: Exception) { + // Is it a 404? + Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") + if ((e as? HttpException)?.code() == HttpURLConnection.HTTP_NOT_FOUND) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Error(e) + } + } + } + .fold( + onSuccess = { it }, + onFailure = { WellknownRetrieverResult.Error(it as Exception) } + ) } - private fun buildWellknownApi(accountProviderUrl: String): WellknownAPI? { - return try { + private fun buildWellknownApi(accountProviderUrl: String): Result { + return runCatchingExceptions { retrofitFactory.create(accountProviderUrl.ensureProtocol()) .create(WellknownAPI::class.java) - } catch (e: Exception) { + }.onFailure { e -> // If the base URL is not valid, we cannot retrieve the well-known data Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl") - null } } } diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt index 9356648a3a..12b961ff9e 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnownBaseConfig +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest @@ -29,9 +30,11 @@ class DefaultSessionWellknownRetrieverTest { getUrlLambda = getUrlLambda, ) assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = null, - identityServer = null, + WellknownRetrieverResult.Success( + WellKnown( + homeServer = null, + identityServer = null, + ) ) ) getUrlLambda.assertions().isCalledOnce() @@ -55,13 +58,15 @@ class DefaultSessionWellknownRetrieverTest { } ) assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = "https://identity.example.org", - ), + WellknownRetrieverResult.Success( + WellKnown( + homeServer = WellKnownBaseConfig( + baseURL = "https://example.org", + ), + identityServer = WellKnownBaseConfig( + baseURL = "https://identity.example.org", + ), + ) ) ) } @@ -81,13 +86,15 @@ class DefaultSessionWellknownRetrieverTest { } ) assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = null, - ), + WellknownRetrieverResult.Success( + WellKnown( + homeServer = WellKnownBaseConfig( + baseURL = "https://example.org", + ), + identityServer = WellKnownBaseConfig( + baseURL = null, + ), + ) ) ) } @@ -110,13 +117,15 @@ class DefaultSessionWellknownRetrieverTest { }, ) assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = "https://identity.example.org", - ), + WellknownRetrieverResult.Success( + WellKnown( + homeServer = WellKnownBaseConfig( + baseURL = "https://example.org", + ), + identityServer = WellKnownBaseConfig( + baseURL = "https://identity.example.org", + ), + ) ) ) } @@ -135,7 +144,7 @@ class DefaultSessionWellknownRetrieverTest { ) } ) - assertThat(sut.getWellKnown()).isNull() + assertThat(sut.getWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test @@ -145,7 +154,7 @@ class DefaultSessionWellknownRetrieverTest { Result.failure(AN_EXCEPTION) } ) - assertThat(sut.getWellKnown()).isNull() + assertThat(sut.getWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test @@ -157,11 +166,13 @@ class DefaultSessionWellknownRetrieverTest { getUrlLambda = getUrlLambda, ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = null, - enforceElementPro = null, - rageshakeUrl = null, - brandColor = null, + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = null, + enforceElementPro = null, + rageshakeUrl = null, + brandColor = null, + ) ) ) getUrlLambda.assertions().isCalledOnce() @@ -183,11 +194,13 @@ class DefaultSessionWellknownRetrieverTest { } ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", - brandColor = "#FF0000", + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = "a_registration_url", + enforceElementPro = true, + rageshakeUrl = "a_rageshake_url", + brandColor = "#FF0000", + ) ) ) } @@ -207,11 +220,13 @@ class DefaultSessionWellknownRetrieverTest { }, ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", - brandColor = null, + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = "a_registration_url", + enforceElementPro = true, + rageshakeUrl = "a_rageshake_url", + brandColor = null, + ) ) ) } @@ -228,7 +243,7 @@ class DefaultSessionWellknownRetrieverTest { ) } ) - assertThat(sut.getElementWellKnown()).isNull() + assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test @@ -238,7 +253,7 @@ class DefaultSessionWellknownRetrieverTest { Result.failure(AN_EXCEPTION) } ) - assertThat(sut.getElementWellKnown()).isNull() + assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } private fun createDefaultSessionWellknownRetriever( diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt index 6c2c141622..d90a02ac96 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt @@ -10,17 +10,18 @@ package io.element.android.features.wellknown.test import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.SessionWellknownRetriever import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.simulateLongTask class FakeSessionWellknownRetriever( - private val getWellKnownResult: () -> WellKnown? = { null }, - private val getElementWellKnownResult: () -> ElementWellKnown? = { null }, + private val getWellKnownResult: () -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, + private val getElementWellKnownResult: () -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, ) : SessionWellknownRetriever { - override suspend fun getWellKnown(): WellKnown? = simulateLongTask { + override suspend fun getWellKnown(): WellknownRetrieverResult = simulateLongTask { getWellKnownResult() } - override suspend fun getElementWellKnown(): ElementWellKnown? = simulateLongTask { + override suspend fun getElementWellKnown(): WellknownRetrieverResult = simulateLongTask { getElementWellKnownResult() } } diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt index c8bbbde26d..52bb1cfa4c 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt @@ -10,17 +10,18 @@ package io.element.android.features.wellknown.test import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.simulateLongTask class FakeWellknownRetriever( - private val getWellKnownResult: (String) -> WellKnown? = { null }, - private val getElementWellKnownResult: (String) -> ElementWellKnown? = { null }, + private val getWellKnownResult: (String) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, + private val getElementWellKnownResult: (String) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, ) : WellknownRetriever { - override suspend fun getWellKnown(baseUrl: String): WellKnown? = simulateLongTask { + override suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult = simulateLongTask { getWellKnownResult(baseUrl) } - override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? = simulateLongTask { + override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult = simulateLongTask { getElementWellKnownResult(baseUrl) } } From dc46e522af6fcf05a376770b982fb457d2df08c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 15:28:31 +0200 Subject: [PATCH 09/17] Improve API of interface WellknownRetriever to be able to distinguish between 404 and other errors. --- .../libraries/matrix/impl/RustMatrixClient.kt | 2 +- .../impl/DefaultSessionWellknownRetriever.kt | 42 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index c646e2ee18..94a562c160 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -286,7 +286,7 @@ class RustMatrixClient( override suspend fun getUrl(url: String): Result = withContext(sessionDispatcher) { runCatchingExceptions { innerClient.getUrl(url) - } + }.mapFailure { it.mapClientException() } } override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) { diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt index 1c7e28e62c..81b4d99134 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt @@ -13,6 +13,7 @@ import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.exception.ClientException import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.SessionWellknownRetriever import io.element.android.libraries.wellknown.api.WellKnown @@ -33,18 +34,9 @@ class DefaultSessionWellknownRetriever( .getUrl(url) .mapCatchingExceptions { val data = String(it) - json().decodeFromString(InternalWellKnown.serializer(), data) + json().decodeFromString(data).map() } - .onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") } - .map { it.map() } - .fold( - onSuccess = { - WellknownRetrieverResult.Success(it) - }, - onFailure = { - WellknownRetrieverResult.Error(it as Exception) - } - ) + .toWellknownRetrieverResult() } override suspend fun getElementWellKnown(): WellknownRetrieverResult { @@ -53,17 +45,23 @@ class DefaultSessionWellknownRetriever( .getUrl(url) .mapCatchingExceptions { val data = String(it) - json().decodeFromString(InternalElementWellKnown.serializer(), data) + json().decodeFromString(data).map() } - .onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") } - .map { it.map() } - .fold( - onSuccess = { - WellknownRetrieverResult.Success(it) - }, - onFailure = { - WellknownRetrieverResult.Error(it as Exception) - } - ) + .toWellknownRetrieverResult() } + + private fun Result.toWellknownRetrieverResult(): WellknownRetrieverResult = fold( + onSuccess = { + WellknownRetrieverResult.Success(it) + }, + onFailure = { + Timber.e(it, "Failed to retrieve Element .well-known from $domain") + // This check on message value is not ideal but this is what we got from the SDK. + if ((it as? ClientException.Generic)?.message?.contains("404") == true) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Error(it as Exception) + } + } + ) } From 08f0e2c79f08bd8b54583f0b85753f3836127a85 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 15:30:09 +0200 Subject: [PATCH 10/17] Update ref. --- enterprise | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise b/enterprise index dac93821a6..867d1118e1 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit dac93821a6f9f9ad1494d3c69c115ef0696eb7ce +Subproject commit 867d1118e157ba89a4f5462f8d9c13e206f10026 From 33ec18c68e3d5d9960562b8acbcb010956994ceb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 16:06:53 +0200 Subject: [PATCH 11/17] Change Preview height to give space for new icons to come. --- .../android/compound/previews/CompoundIconsPreview.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt index 0ee0546ca4..f4aa54d77b 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt @@ -41,13 +41,13 @@ import io.element.android.compound.tokens.generated.CompoundIcons import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewLight() = ElementTheme { IconsCompoundPreview() } -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewRtl() = ElementTheme { CompositionLocalProvider( @@ -59,7 +59,7 @@ internal fun IconsCompoundPreviewRtl() = ElementTheme { } } -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) { IconsCompoundPreview() From c9e65a7c7d0e37777c12a6203178a3e9eef2c611 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 16:11:41 +0200 Subject: [PATCH 12/17] Remove icon preview duplication. We now have IconsCompoundPreview with the same content in only one image. --- .../designsystem/icons/IconsPreview.kt | 50 ++----------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt index e2da9bdcc0..73300d2cdf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt @@ -18,11 +18,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -30,53 +27,12 @@ import io.element.android.libraries.designsystem.theme.components.Text import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -internal class CompoundIconChunkProvider : PreviewParameterProvider { - override val values: Sequence - get() { - val chunks = CompoundIcons.allResIds.chunked(36) - return chunks.mapIndexed { index, chunk -> - IconChunk(index = index + 1, total = chunks.size, icons = chunk.toImmutableList()) - } - .asSequence() - } -} - -internal class OtherIconChunkProvider : PreviewParameterProvider { - override val values: Sequence - get() { - val chunks = iconsOther.chunked(36) - return chunks.mapIndexed { index, chunk -> - IconChunk(index = index + 1, total = chunks.size, icons = chunk.toImmutableList()) - } - .asSequence() - } -} - -internal data class IconChunk( - val index: Int, - val total: Int, - val icons: ImmutableList, -) - @PreviewsDayNight @Composable -internal fun IconsCompoundPreview(@PreviewParameter(CompoundIconChunkProvider::class) chunk: IconChunk) = ElementPreview { +internal fun IconsOtherPreview() = ElementPreview { IconsPreview( - title = "R.drawable.ic_compound_* ${chunk.index}/${chunk.total}", - iconsList = chunk.icons, - iconNameTransform = { name -> - name.removePrefix("ic_compound_") - .replace("_", " ") - } - ) -} - -@PreviewsDayNight -@Composable -internal fun IconsOtherPreview(@PreviewParameter(OtherIconChunkProvider::class) iconChunk: IconChunk) = ElementPreview { - IconsPreview( - title = "R.drawable.ic_* ${iconChunk.index}/${iconChunk.total}", - iconsList = iconChunk.icons, + title = "Other icons", + iconsList = iconsOther.toImmutableList(), iconNameTransform = { name -> name.removePrefix("ic_") .replace("_", " ") From 7829c1fb5412d88674cd5257dc3bfe38c2553ce5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 16:14:06 +0200 Subject: [PATCH 13/17] Add missing icon in the list --- .../io/element/android/libraries/designsystem/icons/IconsList.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index ff98ab5b1e..b9911e25a8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -15,4 +15,5 @@ internal val iconsOther = listOf( R.drawable.ic_notification, R.drawable.ic_stop, R.drawable.pin, + R.drawable.ic_winner, ) From 2e59c057717680e6ac4d077fb8a6d96ea6fc8a49 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 22 Oct 2025 14:33:10 +0000 Subject: [PATCH 14/17] Update screenshots --- .../libraries.designsystem.icons_IconsCompound_Day_0_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Day_1_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Day_2_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Day_3_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Day_4_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Day_5_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_0_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_1_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_2_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_3_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_4_en.png | 3 --- .../libraries.designsystem.icons_IconsCompound_Night_5_en.png | 3 --- .../libraries.designsystem.icons_IconsOther_Day_0_en.png | 4 ++-- .../libraries.designsystem.icons_IconsOther_Night_0_en.png | 4 ++-- 14 files changed, 4 insertions(+), 40 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_5_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_0_en.png deleted file mode 100644 index 761c7dbe49..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c68e00a3f0697a9f179fe8541876fbd371da1d8b55e72c02fbb92d64814863a -size 67653 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png deleted file mode 100644 index 40bafedca7..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0332e0bbea7856b02adb7a660c758a313235351891428fdfb1816d0fa26df4d6 -size 68540 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png deleted file mode 100644 index a7a65b89fb..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c798bec96147f50016dad808d51de76c24339a8b9afe0119993dadd7dbff845 -size 73047 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png deleted file mode 100644 index 0458ab3442..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cf492cd78477c2ce4c296fb6baa2af8baed6549fb51f58c06da9d4181aa8ac9 -size 73796 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png deleted file mode 100644 index 091df51555..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3ab14628f86c883effa49f51c02255a96a0050a9b4261322b2cf596ed5298ed -size 73233 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_5_en.png deleted file mode 100644 index 06fe965ca2..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Day_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2151c4bbd49af17c8af90b946c94ec40b3f51edd25157abc5e87e1b4f7b7922 -size 53698 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_0_en.png deleted file mode 100644 index 30e8ec09d4..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50113ec351ed12a7ee31b5b9333dabbcce242bf762d9d09d41301484e04f33ff -size 64549 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png deleted file mode 100644 index 3098aa900e..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78f6f70dbc14f5b035f6f7d3cc042328be0894771443f188c9bf5581e40bcf59 -size 65190 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png deleted file mode 100644 index c9e430c3a8..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cab2c2d5b07a795080fc887c6c8776a9a8630d9b2f6e5fde7ebb9f5f462fc41c -size 69708 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png deleted file mode 100644 index 8b1e249670..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffb53277e999c57096a8d11a03b2835d30ba7f0e8b83db85d5d1a417bbe35502 -size 70680 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png deleted file mode 100644 index 1e290d9420..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d21b4e71d6d52f1e9d844dbff96dcec86f5a04871b861ce0b619f173edca66cd -size 69772 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_5_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_5_en.png deleted file mode 100644 index 99c55da156..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsCompound_Night_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f209a674db2371d1e85ec2b6d92468f92595f6b87948db19dd7aba8432221b67 -size 51793 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png index 98b70f5c35..279c1855d5 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e71f4f5c0d8f299ff91699612604f5b285160f5830a2b1e3e0c4092e85bd8aa7 -size 14064 +oid sha256:49f3e81d0a713630723c463c8705463e1ba04266624f71d6e29cd7d3a693c115 +size 13789 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png index ed0853de74..e396168a4d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.icons_IconsOther_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e526b48b8f340e5e5c31e0e976286a5d64f27928d3cdf00b3175e5840bc494f3 -size 13282 +oid sha256:76ecf55d5a354374db7b9ac14fb64fae7637b35b2908efcf73163b514710fcb0 +size 13127 From 80c0c1733601a3ea79f624b1c9aba1a9593af12d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Oct 2025 17:25:59 +0200 Subject: [PATCH 15/17] Remove exception --- .../io/element/android/tests/konsist/KonsistPreviewTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index 941fde0124..8120e2ff2e 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -86,7 +86,6 @@ class KonsistPreviewTest { "FocusedEventPreview", "GradientFloatingActionButtonCircleShapePreview", "HeaderFooterPageScrollablePreview", - "IconsCompoundPreview", "IconsOtherPreview", "MarkdownTextComposerEditPreview", "MatrixBadgeAtomInfoPreview", From d8af1eea8949dc8b095020498ec32c3a35474305 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:40:17 +0200 Subject: [PATCH 16/17] fix(deps): update dependency androidx.work:work-runtime-ktx to v2.11.0 (#5590) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff4309c833..f191156732 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ lifecycle = "2.9.2" activity = "1.11.0" media3 = "1.8.0" camera = "1.5.1" -work = "2.10.5" +work = "2.11.0" # Compose compose_bom = "2025.07.00" From 6a8393393e782e88f50fb8753ef68f465eb1f6de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:56:19 +0000 Subject: [PATCH 17/17] fix(deps): update dependency com.posthog:posthog-android to v3.25.0 (#5594) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f191156732..e61114bc65 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -210,7 +210,7 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = color_picker = "io.mhssn:colorpicker:1.0.0" # Analytics -posthog = "com.posthog:posthog-android:3.24.0" +posthog = "com.posthog:posthog-android:3.25.0" sentry = "io.sentry:sentry-android:8.24.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0"