Use client.cachedAvatarUrl(), to be able to render the user avatar faster, and even if there is no network (#2488).

This commit is contained in:
Benoit Marty 2024-03-18 13:55:41 +01:00 committed by Benoit Marty
parent 5c540c4af9
commit 1144d467e5
15 changed files with 105 additions and 124 deletions

View file

@ -42,6 +42,7 @@ import java.io.Closeable
interface MatrixClient : Closeable {
val sessionId: SessionId
val deviceId: String
val userProfile: StateFlow<MatrixUser>
val roomListService: RoomListService
val mediaLoader: MatrixMediaLoader
val sessionCoroutineScope: CoroutineScope
@ -77,8 +78,11 @@ interface MatrixClient : Closeable {
* @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway.
*/
suspend fun logout(ignoreSdkError: Boolean): String?
suspend fun loadUserDisplayName(): Result<String>
suspend fun loadUserAvatarUrl(): Result<String?>
/**
* Retrieve the user profile, will also eventually emit a new value to [userProfile].
*/
suspend fun getUserProfile(): Result<MatrixUser>
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String>
fun roomMembershipObserver(): RoomMembershipObserver

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.user
import io.element.android.libraries.matrix.api.MatrixClient
/**
* Get the current user, as [MatrixUser], using [MatrixClient.loadUserAvatarUrl]
* and [MatrixClient.loadUserDisplayName].
*/
suspend fun MatrixClient.getCurrentUser(): MatrixUser {
val userAvatarUrl = loadUserAvatarUrl().getOrNull()
val userDisplayName = loadUserDisplayName().getOrNull()
return MatrixUser(
userId = sessionId,
displayName = userDisplayName,
avatarUrl = userAvatarUrl,
)
}

View file

@ -73,7 +73,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
@ -249,6 +251,17 @@ class RustMatrixClient(
private val clientDelegateTaskHandle: TaskHandle? = client.setDelegate(clientDelegate)
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(
MatrixUser(
userId = sessionId,
// TODO cache for displayName?
displayName = null,
avatarUrl = client.cachedAvatarUrl(),
)
)
override val userProfile: StateFlow<MatrixUser> = _userProfile
override val ignoredUsersFlow = mxCallbackFlow<ImmutableList<UserId>> {
client.subscribeToIgnoredUsers(object : IgnoredUsersListener {
override fun call(ignoredUserIds: List<String>) {
@ -265,6 +278,10 @@ class RustMatrixClient(
setupVerificationControllerIfNeeded()
}
}.launchIn(sessionCoroutineScope)
sessionCoroutineScope.launch {
// Force a refresh of the profile
getUserProfile()
}
}
override suspend fun getRoom(roomId: RoomId): MatrixRoom? = withContext(sessionDispatcher) {
@ -374,6 +391,9 @@ class RustMatrixClient(
}
}
override suspend fun getUserProfile(): Result<MatrixUser> = getProfile(sessionId)
.onSuccess { _userProfile.tryEmit(it) }
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(sessionDispatcher) {
runCatching {
@ -471,18 +491,6 @@ class RustMatrixClient(
}
}
override suspend fun loadUserDisplayName(): Result<String> = withContext(sessionDispatcher) {
runCatching {
client.displayName()
}
}
override suspend fun loadUserAvatarUrl(): Result<String?> = withContext(sessionDispatcher) {
runCatching {
client.avatarUrl()
}
}
override suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String> = withContext(sessionDispatcher) {
runCatching {
client.uploadMedia(mimeType, data, progressCallback?.toProgressWatcher())

View file

@ -48,14 +48,15 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.TestScope
class FakeMatrixClient(
override val sessionId: SessionId = A_SESSION_ID,
override val deviceId: String = "A_DEVICE_ID",
override val sessionCoroutineScope: CoroutineScope = TestScope(),
private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
private val userAvatarUrl: Result<String> = Result.success(AN_AVATAR_URL),
private val userDisplayName: String? = A_USER_NAME,
private val userAvatarUrl: String? = AN_AVATAR_URL,
override val roomListService: RoomListService = FakeRoomListService(),
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
@ -73,6 +74,8 @@ class FakeMatrixClient(
var removeAvatarCalled: Boolean = false
private set
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(MatrixUser(sessionId, userDisplayName, userAvatarUrl))
override val userProfile: StateFlow<MatrixUser> = _userProfile
override val ignoredUsersFlow: MutableStateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf())
private var ignoreUserResult: Result<Unit> = Result.success(Unit)
@ -140,12 +143,10 @@ class FakeMatrixClient(
override fun close() = Unit
override suspend fun loadUserDisplayName(): Result<String> {
return userDisplayName
}
override suspend fun loadUserAvatarUrl(): Result<String?> {
return userAvatarUrl
override suspend fun getUserProfile(): Result<MatrixUser> = simulateLongTask {
val result = getProfileResults[sessionId]?.getOrNull() ?: MatrixUser(sessionId, userDisplayName, userAvatarUrl)
_userProfile.tryEmit(result)
return Result.success(result)
}
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> {

View file

@ -292,23 +292,30 @@ class DefaultNotificationDrawerManager @Inject constructor(
eventsForSessions.forEach { (sessionId, notifiableEvents) ->
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
val imageLoader = imageLoaderHolder.get(client)
val currentUser = tryOrNull(
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = {
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value
val userAvatarUrl = client.loadUserAvatarUrl().getOrNull()
MatrixUser(
userId = sessionId,
displayName = myUserDisplayName,
avatarUrl = userAvatarUrl
)
}
) ?: MatrixUser(
userId = sessionId,
displayName = sessionId.value,
avatarUrl = null
)
val userFromCache = client.userProfile.value
val currentUser = if (userFromCache.avatarUrl != null && userFromCache.displayName.isNullOrEmpty().not()) {
// We have an avatar and a display name, use it
userFromCache
} else {
tryOrNull(
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = {
client.getUserProfile().getOrNull()
?.let {
// displayName cannot be empty else NotificationCompat.MessagingStyle() will crash
if (it.displayName.isNullOrEmpty()) {
it.copy(displayName = sessionId.value)
} else {
it
}
}
}
) ?: MatrixUser(
userId = sessionId,
displayName = sessionId.value,
avatarUrl = null
)
}
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader)
}