Take into account homeserver capabilities (#6507)
* Take into account homeserver capabilities: add `HomeserverCapabilitiesProvider` to check if the HS allows changing the user's display name or avatar. Also, modify the edit user profile screen to reflect these values. * Add `/myavatar` command. Filter both `/nick` and `/myavatar` commands based on the homeserver capabilities. * Update screenshots * Assume the use can change their display name and avatar url if the capabilities check fails: if they try to change those, the HS will return an error anyway. * Disable also `/myroomname` and `/myroomavatar` based on the HS capabilities. --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
80470b3792
commit
66513bc905
26 changed files with 363 additions and 14 deletions
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.matrix.impl
|
||||
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.HomeserverCapabilitiesProvider
|
||||
import org.matrix.rustcomponents.sdk.HomeserverCapabilities
|
||||
|
||||
class RustHomeserverCapabilitiesProvider(
|
||||
private val homeserverCapabilities: HomeserverCapabilities,
|
||||
) : HomeserverCapabilitiesProvider {
|
||||
override suspend fun refresh(): Result<Unit> = runCatchingExceptions {
|
||||
homeserverCapabilities.refresh()
|
||||
}
|
||||
|
||||
override suspend fun canChangeDisplayName(): Result<Boolean> = runCatchingExceptions {
|
||||
homeserverCapabilities.canChangeDisplayname()
|
||||
}
|
||||
|
||||
override suspend fun canChangeAvatarUrl(): Result<Boolean> = runCatchingExceptions {
|
||||
homeserverCapabilities.canChangeAvatar()
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.core.data.tryOrNull
|
|||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.HomeserverCapabilitiesProvider
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
|
|
@ -835,6 +836,10 @@ class RustMatrixClient(
|
|||
val request = PerformDatabaseVacuumRequestBuilder(sessionId)
|
||||
sessionCoroutineScope.launch { workManagerScheduler.submit(request) }
|
||||
}
|
||||
|
||||
override fun homeserverCapabilities(): HomeserverCapabilitiesProvider {
|
||||
return RustHomeserverCapabilitiesProvider(innerClient.homeserverCapabilities())
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultRoomCreationPowerLevels(isPublic: Boolean, isSpace: Boolean) = PowerLevels(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import dev.zacsweers.metro.ContributesTo
|
|||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.HomeserverCapabilitiesProvider
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
|
|
@ -90,4 +91,9 @@ object SessionMatrixModule {
|
|||
fun providesSpaceService(matrixClient: MatrixClient): SpaceService {
|
||||
return matrixClient.spaceService
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesHomeserverCapabilitiesProvider(matrixClient: MatrixClient): HomeserverCapabilitiesProvider {
|
||||
return matrixClient.homeserverCapabilities()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.matrix.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiHomeserverCapabilities
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class RustHomeserverCapabilitiesProviderTest {
|
||||
@Test
|
||||
fun `refresh calls client refresh`() = runTest {
|
||||
val refreshLambda = lambdaRecorder<Unit> {}
|
||||
val provider = createCapabilitiesProvider(
|
||||
capabilities = FakeFfiHomeserverCapabilities(refresh = refreshLambda),
|
||||
)
|
||||
assertThat(provider.refresh().isSuccess).isTrue()
|
||||
refreshLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `refresh fails when client refresh does`() = runTest {
|
||||
val refreshLambda = lambdaRecorder<Unit> { throw IllegalStateException("Failed to refresh capabilities") }
|
||||
val provider = createCapabilitiesProvider(
|
||||
capabilities = FakeFfiHomeserverCapabilities(refresh = refreshLambda),
|
||||
)
|
||||
assertThat(provider.refresh().isFailure).isTrue()
|
||||
refreshLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canChangeDisplayName returns expected value`() = runTest {
|
||||
val provider = createCapabilitiesProvider(
|
||||
capabilities = FakeFfiHomeserverCapabilities(canChangeDisplayName = { true }),
|
||||
)
|
||||
assertThat(provider.canChangeDisplayName().getOrNull()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canChangeAvatarUrl returns expected value`() = runTest {
|
||||
val provider = createCapabilitiesProvider(
|
||||
capabilities = FakeFfiHomeserverCapabilities(canChangeAvatar = { true }),
|
||||
)
|
||||
assertThat(provider.canChangeAvatarUrl().getOrNull()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canChangeDisplayName returns failure when client throws`() = runTest {
|
||||
val provider = createCapabilitiesProvider(
|
||||
capabilities = FakeFfiHomeserverCapabilities(canChangeDisplayName = { throw IllegalStateException("Failed to get display name capability") }),
|
||||
)
|
||||
assert(provider.canChangeDisplayName().isFailure)
|
||||
}
|
||||
|
||||
private fun createCapabilitiesProvider(
|
||||
capabilities: FakeFfiHomeserverCapabilities = FakeFfiHomeserverCapabilities(),
|
||||
) = RustHomeserverCapabilitiesProvider(capabilities)
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import org.matrix.rustcomponents.sdk.Client
|
|||
import org.matrix.rustcomponents.sdk.ClientDelegate
|
||||
import org.matrix.rustcomponents.sdk.CreateRoomParameters
|
||||
import org.matrix.rustcomponents.sdk.Encryption
|
||||
import org.matrix.rustcomponents.sdk.HomeserverCapabilities
|
||||
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
|
||||
import org.matrix.rustcomponents.sdk.IgnoredUsersListener
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
|
|
@ -50,6 +51,7 @@ class FakeFfiClient(
|
|||
private val homeserverLoginDetailsResult: () -> HomeserverLoginDetails = { lambdaError() },
|
||||
private val getStoreSizesResult: () -> StoreSizes = { lambdaError() },
|
||||
private val createRoomResult: (CreateRoomParameters) -> String = { lambdaError() },
|
||||
private val homeserverCapabilities: HomeserverCapabilities = FakeFfiHomeserverCapabilities(),
|
||||
private val closeResult: () -> Unit = {},
|
||||
) : Client(NoHandle) {
|
||||
override fun userId(): String = userId
|
||||
|
|
@ -103,5 +105,9 @@ class FakeFfiClient(
|
|||
return createRoomResult(request)
|
||||
}
|
||||
|
||||
override fun homeserverCapabilities(): HomeserverCapabilities {
|
||||
return homeserverCapabilities
|
||||
}
|
||||
|
||||
override fun close() = closeResult()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.matrix.impl.fixtures.fakes
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import org.matrix.rustcomponents.sdk.ExtendedProfileFields
|
||||
import org.matrix.rustcomponents.sdk.HomeserverCapabilities
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
|
||||
class FakeFfiHomeserverCapabilities(
|
||||
private val refresh: () -> Unit = { lambdaError() },
|
||||
private val canChangeDisplayName: () -> Boolean = { lambdaError() },
|
||||
private val canChangeAvatar: () -> Boolean = { lambdaError() },
|
||||
private val canChangePassword: () -> Boolean = { lambdaError() },
|
||||
private val canChangeThirdPartyIds: () -> Boolean = { lambdaError() },
|
||||
private val canGetLoginToken: () -> Boolean = { lambdaError() },
|
||||
private val forgetsRoomWhenLeaving: () -> Boolean = { lambdaError() },
|
||||
private val extendedProfileFields: () -> ExtendedProfileFields = { lambdaError() },
|
||||
) : HomeserverCapabilities(NoHandle) {
|
||||
override suspend fun refresh() = refresh.invoke()
|
||||
override suspend fun canChangeDisplayname(): Boolean = canChangeDisplayName.invoke()
|
||||
override suspend fun canChangeAvatar(): Boolean = canChangeAvatar.invoke()
|
||||
override suspend fun canChangePassword(): Boolean = canChangePassword.invoke()
|
||||
override suspend fun canChangeThirdpartyIds(): Boolean = canChangeThirdPartyIds.invoke()
|
||||
override suspend fun canGetLoginToken(): Boolean = canGetLoginToken.invoke()
|
||||
override suspend fun forgetsRoomWhenLeaving(): Boolean = forgetsRoomWhenLeaving.invoke()
|
||||
override suspend fun extendedProfileFields(): ExtendedProfileFields = extendedProfileFields.invoke()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue