Merge branch 'develop' into feature/bma/metro070

This commit is contained in:
Benoit Marty 2025-10-23 11:30:25 +02:00 committed by GitHub
commit 52dffa5418
46 changed files with 306 additions and 233 deletions

@ -1 +1 @@
Subproject commit dac93821a6f9f9ad1494d3c69c115ef0696eb7ce Subproject commit 867d1118e157ba89a4f5462f8d9c13e206f10026

View file

@ -39,7 +39,7 @@ class DefaultAccountProviderAccessControl(
// Ensure that Element Pro is not required for this account provider // Ensure that Element Pro is not required for this account provider
val wellKnown = wellknownRetriever.getElementWellKnown( val wellKnown = wellknownRetriever.getElementWellKnown(
baseUrl = accountProviderUrl.ensureProtocol(), baseUrl = accountProviderUrl.ensureProtocol(),
) ).dataOrNull()
if (wellKnown?.enforceElementPro == true) { if (wellKnown?.enforceElementPro == true) {
throw AccountProviderAccessException.NeedElementProException( throw AccountProviderAccessException.NeedElementProException(
unauthorisedAccountProviderTitle = title, unauthorisedAccountProviderTitle = title,

View file

@ -46,7 +46,7 @@ class HomeserverResolver(
wellknownRetriever.getWellKnown(url) wellknownRetriever.getWellKnown(url)
} }
} }
val isValid = wellKnown?.isValid().orFalse() val isValid = wellKnown?.dataOrNull()?.isValid().orFalse()
if (isValid) { if (isValid) {
// Emit the list as soon as possible // Emit the list as soon as possible
currentList.add( currentList.add(

View file

@ -28,7 +28,7 @@ class DefaultWebClientUrlForAuthenticationRetriever(
Timber.w("Temporary account creation flow is only supported on matrix.org") Timber.w("Temporary account creation flow is only supported on matrix.org")
throw AccountCreationNotSupported() throw AccountCreationNotSupported()
} }
val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl) val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl).dataOrNull()
?: throw AccountCreationNotSupported() ?: throw AccountCreationNotSupported()
val registrationHelperUrl = wellknown.registrationHelperUrl val registrationHelperUrl = wellknown.registrationHelperUrl
return if (registrationHelperUrl != null) { return if (registrationHelperUrl != null) {

View file

@ -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_2
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL 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.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows import org.junit.Assert.assertThrows
import org.junit.Test import org.junit.Test
@ -155,7 +156,13 @@ class DefaultAccountProviderAccessControlTest {
defaultHomeserverListResult = { allowedAccountProviders }, defaultHomeserverListResult = { allowedAccountProviders },
), ),
wellknownRetriever = FakeWellknownRetriever( wellknownRetriever = FakeWellknownRetriever(
getElementWellKnownResult = { elementWellKnown }, getElementWellKnownResult = {
if (elementWellKnown == null) {
WellknownRetrieverResult.NotFound
} else {
WellknownRetrieverResult.Success(elementWellKnown)
}
},
), ),
) )

View file

@ -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.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellknownRetriever 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.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -114,9 +115,11 @@ class ChangeServerPresenterTest {
@Test @Test
fun `present - change server element pro required error`() = runTest { fun `present - change server element pro required error`() = runTest {
val getElementWellKnownResult = lambdaRecorder<String, ElementWellKnown> { val getElementWellKnownResult = lambdaRecorder<String, WellknownRetrieverResult<ElementWellKnown>> {
anElementWellKnown( WellknownRetrieverResult.Success(
enforceElementPro = true, anElementWellKnown(
enforceElementPro = true,
)
) )
} }
createPresenter( createPresenter(

View file

@ -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.matrix.test.A_HOMESERVER_URL
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellKnownBaseConfig 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.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -94,12 +95,12 @@ class SearchAccountProviderPresenterTest {
@Test @Test
fun `present - enter text one result with wellknown`() = runTest { fun `present - enter text one result with wellknown`() = runTest {
val getWellKnownResult = lambdaRecorder<String, WellKnown> { val getWellKnownResult = lambdaRecorder<String, WellknownRetrieverResult<WellKnown>> {
when (it) { when (it) {
"https://test.org" -> error("not found") "https://test.org" -> WellknownRetrieverResult.NotFound
"https://test.com" -> error("not found") "https://test.com" -> WellknownRetrieverResult.NotFound
"https://test.io" -> aWellKnown() "https://test.io" -> WellknownRetrieverResult.Success(aWellKnown())
"https://test" -> error("not found") "https://test" -> WellknownRetrieverResult.NotFound
else -> error("should not happen") else -> error("should not happen")
} }
} }
@ -138,12 +139,12 @@ class SearchAccountProviderPresenterTest {
@Test @Test
fun `present - enter text two results with wellknown`() = runTest { fun `present - enter text two results with wellknown`() = runTest {
val getWellKnownResult = lambdaRecorder<String, WellKnown> { val getWellKnownResult = lambdaRecorder<String, WellknownRetrieverResult<WellKnown>> {
when (it) { when (it) {
"https://test.org" -> aWellKnown() "https://test.org" -> WellknownRetrieverResult.Success(aWellKnown())
"https://test.com" -> error("not found") "https://test.com" -> WellknownRetrieverResult.NotFound
"https://test.io" -> aWellKnown() "https://test.io" -> WellknownRetrieverResult.Success(aWellKnown())
"https://test" -> error("not found") "https://test" -> WellknownRetrieverResult.NotFound
else -> error("should not happen") else -> error("should not happen")
} }
} }

View file

@ -164,7 +164,7 @@ class RoomDetailsPresenter(
val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value) val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
val canShowSecurityAndPrivacy by remember { val canShowSecurityAndPrivacy by remember {
derivedStateOf { derivedStateOf {
isKnockRequestsEnabled && roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny
} }
} }

View file

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

View file

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

View file

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

View file

@ -81,6 +81,7 @@ fun SecurityAndPrivacyView(
modifier = Modifier.padding(top = 24.dp), modifier = Modifier.padding(top = 24.dp),
edited = state.editedSettings.roomAccess, edited = state.editedSettings.roomAccess,
saved = state.savedSettings.roomAccess, saved = state.savedSettings.roomAccess,
isKnockEnabled = state.isKnockEnabled,
onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) },
) )
} }
@ -176,6 +177,7 @@ private fun SecurityAndPrivacySection(
private fun RoomAccessSection( private fun RoomAccessSection(
edited: SecurityAndPrivacyRoomAccess, edited: SecurityAndPrivacyRoomAccess,
saved: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess,
isKnockEnabled: Boolean,
onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -189,12 +191,18 @@ private fun RoomAccessSection(
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly),
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
) )
ListItem( // Show Ask to join option in two cases:
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, // - the Knock FF is enabled
supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, // - AskToJoin is the current saved value
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), if (saved == SecurityAndPrivacyRoomAccess.AskToJoin || isKnockEnabled) {
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, 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( ListItem(
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) }, headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) },
supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) }, supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) },

View file

@ -719,10 +719,6 @@ class RoomDetailsPresenterTest {
val presenter = createRoomDetailsPresenter(room = room, featureFlagService = featureFlagService) val presenter = createRoomDetailsPresenter(room = room, featureFlagService = featureFlagService)
presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) {
skipItems(1) skipItems(1)
with(awaitItem()) {
assertThat(canShowSecurityAndPrivacy).isFalse()
}
featureFlagService.setFeatureEnabled(FeatureFlags.Knock, true)
with(awaitItem()) { with(awaitItem()) {
assertThat(canShowSecurityAndPrivacy).isTrue() assertThat(canShowSecurityAndPrivacy).isTrue()
} }

View file

@ -10,6 +10,9 @@ package io.element.android.features.roomdetails.impl.securityandprivacy
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
@ -38,6 +41,7 @@ class SecurityAndPrivacyPresenterTest {
assertThat(showRoomVisibilitySections).isFalse() assertThat(showRoomVisibilitySections).isFalse()
assertThat(showHistoryVisibilitySection).isFalse() assertThat(showHistoryVisibilitySection).isFalse()
assertThat(showEncryptionSection).isFalse() assertThat(showEncryptionSection).isFalse()
assertThat(isKnockEnabled).isFalse()
} }
with(awaitItem()) { with(awaitItem()) {
assertThat(editedSettings).isEqualTo(savedSettings) assertThat(editedSettings).isEqualTo(savedSettings)
@ -48,6 +52,7 @@ class SecurityAndPrivacyPresenterTest {
assertThat(showRoomVisibilitySections).isFalse() assertThat(showRoomVisibilitySections).isFalse()
assertThat(showHistoryVisibilitySection).isTrue() assertThat(showHistoryVisibilitySection).isTrue()
assertThat(showEncryptionSection).isTrue() assertThat(showEncryptionSection).isTrue()
assertThat(isKnockEnabled).isFalse()
} }
} }
} }
@ -56,14 +61,14 @@ class SecurityAndPrivacyPresenterTest {
fun `present - room info change updates saved and edited settings`() = runTest { fun `present - room info change updates saved and edited settings`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
initialRoomInfo = aRoomInfo( initialRoomInfo = aRoomInfo(
joinRule = JoinRule.Public, joinRule = JoinRule.Public,
historyVisibility = RoomHistoryVisibility.WorldReadable, historyVisibility = RoomHistoryVisibility.WorldReadable,
canonicalAlias = A_ROOM_ALIAS, canonicalAlias = A_ROOM_ALIAS,
)
) )
) )
)
val presenter = createSecurityAndPrivacyPresenter(room = room) val presenter = createSecurityAndPrivacyPresenter(room = room)
presenter.test { presenter.test {
skipItems(1) skipItems(1)
@ -163,10 +168,10 @@ class SecurityAndPrivacyPresenterTest {
fun `present - room visibility loading and change`() = runTest { fun `present - room visibility loading and change`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared)
) )
) )
val presenter = createSecurityAndPrivacyPresenter(room = room) val presenter = createSecurityAndPrivacyPresenter(room = room)
presenter.test { presenter.test {
@ -212,10 +217,10 @@ class SecurityAndPrivacyPresenterTest {
val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) } val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared)
), ),
enableEncryptionResult = enableEncryptionLambda, enableEncryptionResult = enableEncryptionLambda,
updateJoinRuleResult = updateJoinRuleLambda, updateJoinRuleResult = updateJoinRuleLambda,
updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda,
@ -279,10 +284,10 @@ class SecurityAndPrivacyPresenterTest {
val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) } val updateRoomHistoryVisibilityLambda = lambdaRecorder<RoomHistoryVisibility, Result<Unit>> { Result.success(Unit) }
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canSendStateResult = { _, _ -> Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) },
getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, getRoomVisibilityResult = { Result.success(RoomVisibility.Private) },
initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private)
), ),
enableEncryptionResult = enableEncryptionLambda, enableEncryptionResult = enableEncryptionLambda,
updateJoinRuleResult = updateJoinRuleLambda, updateJoinRuleResult = updateJoinRuleLambda,
updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda,
@ -323,7 +328,8 @@ class SecurityAndPrivacyPresenterTest {
) )
// Saved settings are updated 2 times to match the edited settings // Saved settings are updated 2 times to match the edited settings
skipItems(3) skipItems(3)
with(awaitItem()) { val state = awaitItem()
with(state) {
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java)
assertThat(savedSettings.isVisibleInRoomDirectory).isNotEqualTo(editedSettings.isVisibleInRoomDirectory) assertThat(savedSettings.isVisibleInRoomDirectory).isNotEqualTo(editedSettings.isVisibleInRoomDirectory)
assertThat(canBeSaved).isTrue() assertThat(canBeSaved).isTrue()
@ -332,6 +338,26 @@ class SecurityAndPrivacyPresenterTest {
assert(updateJoinRuleLambda).isCalledOnce() assert(updateJoinRuleLambda).isCalledOnce()
assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce()
assert(updateRoomHistoryVisibilityLambda).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(), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
): SecurityAndPrivacyPresenter { ): SecurityAndPrivacyPresenter {
return SecurityAndPrivacyPresenter( return SecurityAndPrivacyPresenter(
room = room, room = room,
matrixClient = FakeMatrixClient( matrixClient = FakeMatrixClient(
userIdServerNameLambda = { serverName }, userIdServerNameLambda = { serverName },
), ),
navigator = navigator navigator = navigator,
featureFlagService = featureFlagService,
) )
} }
} }

View file

@ -20,7 +20,7 @@ lifecycle = "2.9.2"
activity = "1.11.0" activity = "1.11.0"
media3 = "1.8.0" media3 = "1.8.0"
camera = "1.5.1" camera = "1.5.1"
work = "2.10.5" work = "2.11.0"
# Compose # Compose
compose_bom = "2025.07.00" compose_bom = "2025.07.00"
@ -210,7 +210,7 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref =
color_picker = "io.mhssn:colorpicker:1.0.0" color_picker = "io.mhssn:colorpicker:1.0.0"
# Analytics # 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" sentry = "io.sentry:sentry-android:8.24.0"
# main branch can be tested replacing the version with main-SNAPSHOT # main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0"

View file

@ -41,13 +41,13 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@Preview(widthDp = 730, heightDp = 1800) @Preview(widthDp = 730, heightDp = 1920)
@Composable @Composable
internal fun IconsCompoundPreviewLight() = ElementTheme { internal fun IconsCompoundPreviewLight() = ElementTheme {
IconsCompoundPreview() IconsCompoundPreview()
} }
@Preview(widthDp = 730, heightDp = 1800) @Preview(widthDp = 730, heightDp = 1920)
@Composable @Composable
internal fun IconsCompoundPreviewRtl() = ElementTheme { internal fun IconsCompoundPreviewRtl() = ElementTheme {
CompositionLocalProvider( CompositionLocalProvider(
@ -59,7 +59,7 @@ internal fun IconsCompoundPreviewRtl() = ElementTheme {
} }
} }
@Preview(widthDp = 730, heightDp = 1800) @Preview(widthDp = 730, heightDp = 1920)
@Composable @Composable
internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) { internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) {
IconsCompoundPreview() IconsCompoundPreview()

View file

@ -15,4 +15,5 @@ internal val iconsOther = listOf(
R.drawable.ic_notification, R.drawable.ic_notification,
R.drawable.ic_stop, R.drawable.ic_stop,
R.drawable.pin, R.drawable.pin,
R.drawable.ic_winner,
) )

View file

@ -18,11 +18,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign 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 androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme 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.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon 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.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
internal class CompoundIconChunkProvider : PreviewParameterProvider<IconChunk> {
override val values: Sequence<IconChunk>
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<IconChunk> {
override val values: Sequence<IconChunk>
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<Int>,
)
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun IconsCompoundPreview(@PreviewParameter(CompoundIconChunkProvider::class) chunk: IconChunk) = ElementPreview { internal fun IconsOtherPreview() = ElementPreview {
IconsPreview( IconsPreview(
title = "R.drawable.ic_compound_* ${chunk.index}/${chunk.total}", title = "Other icons",
iconsList = chunk.icons, iconsList = iconsOther.toImmutableList(),
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,
iconNameTransform = { name -> iconNameTransform = { name ->
name.removePrefix("ic_") name.removePrefix("ic_")
.replace("_", " ") .replace("_", " ")

View file

@ -286,7 +286,7 @@ class RustMatrixClient(
override suspend fun getUrl(url: String): Result<ByteArray> = withContext(sessionDispatcher) { override suspend fun getUrl(url: String): Result<ByteArray> = withContext(sessionDispatcher) {
runCatchingExceptions { runCatchingExceptions {
innerClient.getUrl(url) innerClient.getUrl(url)
} }.mapFailure { it.mapClientException() }
} }
override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) { override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) {

View file

@ -8,6 +8,6 @@
package io.element.android.libraries.wellknown.api package io.element.android.libraries.wellknown.api
interface SessionWellknownRetriever { interface SessionWellknownRetriever {
suspend fun getWellKnown(): WellKnown? suspend fun getWellKnown(): WellknownRetrieverResult<WellKnown>
suspend fun getElementWellKnown(): ElementWellKnown? suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown>
} }

View file

@ -8,6 +8,6 @@
package io.element.android.libraries.wellknown.api package io.element.android.libraries.wellknown.api
interface WellknownRetriever { interface WellknownRetriever {
suspend fun getWellKnown(baseUrl: String): WellKnown? suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult<WellKnown>
suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown>
} }

View file

@ -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<out T> {
/**
* Well-known data has been successfully retrieved.
*/
data class Success<out T>(val data: T) : WellknownRetrieverResult<T>
/**
* Well-known data is not found (file does not exist server side, we got a 404).
*/
data object NotFound : WellknownRetrieverResult<Nothing>
/**
* Any other error.
*/
data class Error(val exception: Exception) : WellknownRetrieverResult<Nothing>
fun dataOrNull(): T? = when (this) {
is Success<T> -> data
is Error -> null
NotFound -> null
}
}

View file

@ -12,9 +12,11 @@ import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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.ElementWellKnown
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import timber.log.Timber import timber.log.Timber
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
@ -24,29 +26,40 @@ class DefaultSessionWellknownRetriever(
) : SessionWellknownRetriever { ) : SessionWellknownRetriever {
private val domain by lazy { matrixClient.userIdServerName() } private val domain by lazy { matrixClient.userIdServerName() }
override suspend fun getWellKnown(): WellKnown? { override suspend fun getWellKnown(): WellknownRetrieverResult<WellKnown> {
val url = "https://$domain/.well-known/matrix/client" val url = "https://$domain/.well-known/matrix/client"
return matrixClient return matrixClient
.getUrl(url) .getUrl(url)
.mapCatchingExceptions { .mapCatchingExceptions {
val data = String(it) val data = String(it)
json().decodeFromString(InternalWellKnown.serializer(), data) json().decodeFromString<InternalWellKnown>(data).map()
} }
.onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") } .toWellknownRetrieverResult()
.map { it.map() }
.getOrNull()
} }
override suspend fun getElementWellKnown(): ElementWellKnown? { override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> {
val url = "https://$domain/.well-known/element/element.json" val url = "https://$domain/.well-known/element/element.json"
return matrixClient return matrixClient
.getUrl(url) .getUrl(url)
.mapCatchingExceptions { .mapCatchingExceptions {
val data = String(it) val data = String(it)
json().decodeFromString(InternalElementWellKnown.serializer(), data) json().decodeFromString<InternalElementWellKnown>(data).map()
} }
.onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") } .toWellknownRetrieverResult()
.map { it.map() }
.getOrNull()
} }
private fun <T> Result<T>.toWellknownRetrieverResult(): WellknownRetrieverResult<T> = 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)
}
}
)
} }

View file

@ -9,45 +9,71 @@ package io.element.android.libraries.wellknown.impl
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.ensureProtocol
import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.network.RetrofitFactory
import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.libraries.wellknown.api.WellknownRetriever
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import retrofit2.HttpException
import timber.log.Timber import timber.log.Timber
import java.net.HttpURLConnection
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultWellknownRetriever( class DefaultWellknownRetriever(
private val retrofitFactory: RetrofitFactory, private val retrofitFactory: RetrofitFactory,
) : WellknownRetriever { ) : WellknownRetriever {
override suspend fun getWellKnown(baseUrl: String): WellKnown? { override suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult<WellKnown> {
val wellknownApi = buildWellknownApi(baseUrl) ?: return null return buildWellknownApi(baseUrl)
return try { .map { wellknownApi ->
wellknownApi.getWellKnown().map() try {
} catch (e: Exception) { val result = wellknownApi.getWellKnown().map()
Timber.e(e, "Failed to retrieve well-known data for $baseUrl") WellknownRetrieverResult.Success(result)
null } 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? { override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> {
val wellknownApi = buildWellknownApi(baseUrl) ?: return null return buildWellknownApi(baseUrl)
return try { .map { wellknownApi ->
wellknownApi.getElementWellKnown().map() try {
} catch (e: Exception) { val result = wellknownApi.getElementWellKnown().map()
Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") WellknownRetrieverResult.Success(result)
null } 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? { private fun buildWellknownApi(accountProviderUrl: String): Result<WellknownAPI> {
return try { return runCatchingExceptions {
retrofitFactory.create(accountProviderUrl.ensureProtocol()) retrofitFactory.create(accountProviderUrl.ensureProtocol())
.create(WellknownAPI::class.java) .create(WellknownAPI::class.java)
} catch (e: Exception) { }.onFailure { e ->
// If the base URL is not valid, we cannot retrieve the well-known data // If the base URL is not valid, we cannot retrieve the well-known data
Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl") Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl")
null
} }
} }
} }

View file

@ -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.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellKnownBaseConfig 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.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -29,9 +30,11 @@ class DefaultSessionWellknownRetrieverTest {
getUrlLambda = getUrlLambda, getUrlLambda = getUrlLambda,
) )
assertThat(sut.getWellKnown()).isEqualTo( assertThat(sut.getWellKnown()).isEqualTo(
WellKnown( WellknownRetrieverResult.Success(
homeServer = null, WellKnown(
identityServer = null, homeServer = null,
identityServer = null,
)
) )
) )
getUrlLambda.assertions().isCalledOnce() getUrlLambda.assertions().isCalledOnce()
@ -55,13 +58,15 @@ class DefaultSessionWellknownRetrieverTest {
} }
) )
assertThat(sut.getWellKnown()).isEqualTo( assertThat(sut.getWellKnown()).isEqualTo(
WellKnown( WellknownRetrieverResult.Success(
homeServer = WellKnownBaseConfig( WellKnown(
baseURL = "https://example.org", homeServer = WellKnownBaseConfig(
), baseURL = "https://example.org",
identityServer = WellKnownBaseConfig( ),
baseURL = "https://identity.example.org", identityServer = WellKnownBaseConfig(
), baseURL = "https://identity.example.org",
),
)
) )
) )
} }
@ -81,13 +86,15 @@ class DefaultSessionWellknownRetrieverTest {
} }
) )
assertThat(sut.getWellKnown()).isEqualTo( assertThat(sut.getWellKnown()).isEqualTo(
WellKnown( WellknownRetrieverResult.Success(
homeServer = WellKnownBaseConfig( WellKnown(
baseURL = "https://example.org", homeServer = WellKnownBaseConfig(
), baseURL = "https://example.org",
identityServer = WellKnownBaseConfig( ),
baseURL = null, identityServer = WellKnownBaseConfig(
), baseURL = null,
),
)
) )
) )
} }
@ -110,13 +117,15 @@ class DefaultSessionWellknownRetrieverTest {
}, },
) )
assertThat(sut.getWellKnown()).isEqualTo( assertThat(sut.getWellKnown()).isEqualTo(
WellKnown( WellknownRetrieverResult.Success(
homeServer = WellKnownBaseConfig( WellKnown(
baseURL = "https://example.org", homeServer = WellKnownBaseConfig(
), baseURL = "https://example.org",
identityServer = WellKnownBaseConfig( ),
baseURL = "https://identity.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 @Test
@ -145,7 +154,7 @@ class DefaultSessionWellknownRetrieverTest {
Result.failure(AN_EXCEPTION) Result.failure(AN_EXCEPTION)
} }
) )
assertThat(sut.getWellKnown()).isNull() assertThat(sut.getWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
} }
@Test @Test
@ -157,11 +166,13 @@ class DefaultSessionWellknownRetrieverTest {
getUrlLambda = getUrlLambda, getUrlLambda = getUrlLambda,
) )
assertThat(sut.getElementWellKnown()).isEqualTo( assertThat(sut.getElementWellKnown()).isEqualTo(
ElementWellKnown( WellknownRetrieverResult.Success(
registrationHelperUrl = null, ElementWellKnown(
enforceElementPro = null, registrationHelperUrl = null,
rageshakeUrl = null, enforceElementPro = null,
brandColor = null, rageshakeUrl = null,
brandColor = null,
)
) )
) )
getUrlLambda.assertions().isCalledOnce() getUrlLambda.assertions().isCalledOnce()
@ -183,11 +194,13 @@ class DefaultSessionWellknownRetrieverTest {
} }
) )
assertThat(sut.getElementWellKnown()).isEqualTo( assertThat(sut.getElementWellKnown()).isEqualTo(
ElementWellKnown( WellknownRetrieverResult.Success(
registrationHelperUrl = "a_registration_url", ElementWellKnown(
enforceElementPro = true, registrationHelperUrl = "a_registration_url",
rageshakeUrl = "a_rageshake_url", enforceElementPro = true,
brandColor = "#FF0000", rageshakeUrl = "a_rageshake_url",
brandColor = "#FF0000",
)
) )
) )
} }
@ -207,11 +220,13 @@ class DefaultSessionWellknownRetrieverTest {
}, },
) )
assertThat(sut.getElementWellKnown()).isEqualTo( assertThat(sut.getElementWellKnown()).isEqualTo(
ElementWellKnown( WellknownRetrieverResult.Success(
registrationHelperUrl = "a_registration_url", ElementWellKnown(
enforceElementPro = true, registrationHelperUrl = "a_registration_url",
rageshakeUrl = "a_rageshake_url", enforceElementPro = true,
brandColor = null, 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 @Test
@ -238,7 +253,7 @@ class DefaultSessionWellknownRetrieverTest {
Result.failure(AN_EXCEPTION) Result.failure(AN_EXCEPTION)
} }
) )
assertThat(sut.getElementWellKnown()).isNull() assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
} }
private fun createDefaultSessionWellknownRetriever( private fun createDefaultSessionWellknownRetriever(

View file

@ -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.ElementWellKnown
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
class FakeSessionWellknownRetriever( class FakeSessionWellknownRetriever(
private val getWellKnownResult: () -> WellKnown? = { null }, private val getWellKnownResult: () -> WellknownRetrieverResult<WellKnown> = { WellknownRetrieverResult.NotFound },
private val getElementWellKnownResult: () -> ElementWellKnown? = { null }, private val getElementWellKnownResult: () -> WellknownRetrieverResult<ElementWellKnown> = { WellknownRetrieverResult.NotFound },
) : SessionWellknownRetriever { ) : SessionWellknownRetriever {
override suspend fun getWellKnown(): WellKnown? = simulateLongTask { override suspend fun getWellKnown(): WellknownRetrieverResult<WellKnown> = simulateLongTask {
getWellKnownResult() getWellKnownResult()
} }
override suspend fun getElementWellKnown(): ElementWellKnown? = simulateLongTask { override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> = simulateLongTask {
getElementWellKnownResult() getElementWellKnownResult()
} }
} }

View file

@ -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.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.libraries.wellknown.api.WellknownRetriever
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
class FakeWellknownRetriever( class FakeWellknownRetriever(
private val getWellKnownResult: (String) -> WellKnown? = { null }, private val getWellKnownResult: (String) -> WellknownRetrieverResult<WellKnown> = { WellknownRetrieverResult.NotFound },
private val getElementWellKnownResult: (String) -> ElementWellKnown? = { null }, private val getElementWellKnownResult: (String) -> WellknownRetrieverResult<ElementWellKnown> = { WellknownRetrieverResult.NotFound },
) : WellknownRetriever { ) : WellknownRetriever {
override suspend fun getWellKnown(baseUrl: String): WellKnown? = simulateLongTask { override suspend fun getWellKnown(baseUrl: String): WellknownRetrieverResult<WellKnown> = simulateLongTask {
getWellKnownResult(baseUrl) getWellKnownResult(baseUrl)
} }
override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? = simulateLongTask { override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> = simulateLongTask {
getElementWellKnownResult(baseUrl) getElementWellKnownResult(baseUrl)
} }
} }

View file

@ -86,7 +86,6 @@ class KonsistPreviewTest {
"FocusedEventPreview", "FocusedEventPreview",
"GradientFloatingActionButtonCircleShapePreview", "GradientFloatingActionButtonCircleShapePreview",
"HeaderFooterPageScrollablePreview", "HeaderFooterPageScrollablePreview",
"IconsCompoundPreview",
"IconsOtherPreview", "IconsOtherPreview",
"MarkdownTextComposerEditPreview", "MarkdownTextComposerEditPreview",
"MatrixBadgeAtomInfoPreview", "MatrixBadgeAtomInfoPreview",

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d627cd374fe0746d2fc84fffecf6a1636fb68784ed5edae3b830fe4ee2b3751a oid sha256:9807bf17918c39f2e6e4cddf87237fa26f63732f48210b0f803114bcf4c98451
size 61398 size 61360

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ad13a47522e6365928e7209e515a47a7e4fafc28ad87361dffdf92c95b3ff06
size 62563

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:70979d3b81a521b6208da2f3a95187bc7b3ae0ea52e3dd35e2bbd91df14c007f oid sha256:ade5d824f6f466960931abcbc86cb9a286cbf6bd9a3adf875f446dc094e291d5
size 63318 size 63393

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2cfda5b044d11e4e75fa83c9bad7b7f03a328422881cbededfaee8ab091d1db
size 64586

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c68e00a3f0697a9f179fe8541876fbd371da1d8b55e72c02fbb92d64814863a
size 67653

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0332e0bbea7856b02adb7a660c758a313235351891428fdfb1816d0fa26df4d6
size 68540

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c798bec96147f50016dad808d51de76c24339a8b9afe0119993dadd7dbff845
size 73047

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5cf492cd78477c2ce4c296fb6baa2af8baed6549fb51f58c06da9d4181aa8ac9
size 73796

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3ab14628f86c883effa49f51c02255a96a0050a9b4261322b2cf596ed5298ed
size 73233

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2151c4bbd49af17c8af90b946c94ec40b3f51edd25157abc5e87e1b4f7b7922
size 53698

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:50113ec351ed12a7ee31b5b9333dabbcce242bf762d9d09d41301484e04f33ff
size 64549

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:78f6f70dbc14f5b035f6f7d3cc042328be0894771443f188c9bf5581e40bcf59
size 65190

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cab2c2d5b07a795080fc887c6c8776a9a8630d9b2f6e5fde7ebb9f5f462fc41c
size 69708

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ffb53277e999c57096a8d11a03b2835d30ba7f0e8b83db85d5d1a417bbe35502
size 70680

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d21b4e71d6d52f1e9d844dbff96dcec86f5a04871b861ce0b619f173edca66cd
size 69772

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f209a674db2371d1e85ec2b6d92468f92595f6b87948db19dd7aba8432221b67
size 51793

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e71f4f5c0d8f299ff91699612604f5b285160f5830a2b1e3e0c4092e85bd8aa7 oid sha256:49f3e81d0a713630723c463c8705463e1ba04266624f71d6e29cd7d3a693c115
size 14064 size 13789

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e526b48b8f340e5e5c31e0e976286a5d64f27928d3cdf00b3175e5840bc494f3 oid sha256:76ecf55d5a354374db7b9ac14fb64fae7637b35b2908efcf73163b514710fcb0
size 13282 size 13127