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:
Jorge Martin Espinosa 2026-04-15 14:29:41 +02:00 committed by GitHub
parent 80470b3792
commit 66513bc905
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 363 additions and 14 deletions

View file

@ -21,6 +21,8 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.extensions.runCatchingExceptions
@ -56,6 +58,7 @@ class LoggedInPresenter(
private val analyticsService: AnalyticsService,
private val encryptionService: EncryptionService,
private val buildMeta: BuildMeta,
private val networkMonitor: NetworkMonitor,
) : Presenter<LoggedInState> {
@Composable
override fun present(): LoggedInState {
@ -107,6 +110,14 @@ class LoggedInPresenter(
}.launchIn(this)
}
val networkConnectivity by networkMonitor.connectivity.collectAsState()
LaunchedEffect(networkConnectivity) {
if (networkConnectivity == NetworkStatus.Connected) {
// Refresh homeserver capabilities when the network is back
matrixClient.homeserverCapabilities().refresh()
}
}
fun handleEvent(event: LoggedInEvents) {
when (event) {
is LoggedInEvents.CloseErrorDialog -> {

View file

@ -14,6 +14,8 @@ import app.cash.turbine.ReceiveTurbine
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
@ -27,6 +29,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeHomeserverCapabilitiesProvider
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
@ -109,6 +112,7 @@ class LoggedInPresenterTest {
val verificationService = FakeSessionVerificationService()
val encryptionService = FakeEncryptionService()
val buildMeta = aBuildMeta()
val networkMonitor = FakeNetworkMonitor()
LoggedInPresenter(
matrixClient = FakeMatrixClient(
roomListService = roomListService,
@ -122,6 +126,7 @@ class LoggedInPresenterTest {
analyticsService = analyticsService,
encryptionService = encryptionService,
buildMeta = buildMeta,
networkMonitor = networkMonitor,
).test {
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
@ -319,6 +324,27 @@ class LoggedInPresenterTest {
}
}
@Test
fun `present - refreshes homeserver capabilities when network is back`() = runTest {
val refreshLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val matrixClient = FakeMatrixClient(
homeserverCapabilitiesProvider = FakeHomeserverCapabilitiesProvider(refresh = refreshLambda),
accountManagementUrlResult = { Result.success(null) },
)
val networkMonitor = FakeNetworkMonitor()
createLoggedInPresenter(
matrixClient = matrixClient,
networkMonitor = networkMonitor,
).test {
awaitItem()
networkMonitor.connectivity.value = NetworkStatus.Connected
advanceUntilIdle()
refreshLambda.assertions().isCalledOnce()
}
}
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
skipItems(1)
return awaitItem()
@ -334,6 +360,7 @@ class LoggedInPresenterTest {
accountManagementUrlResult = { Result.success(null) },
),
buildMeta: BuildMeta = aBuildMeta(),
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
): LoggedInPresenter {
return LoggedInPresenter(
matrixClient = matrixClient,
@ -343,6 +370,7 @@ class LoggedInPresenterTest {
analyticsService = analyticsService,
encryptionService = encryptionService,
buildMeta = buildMeta,
networkMonitor = networkMonitor,
)
}
}