Reduce room list placeholder lags

This commit is contained in:
valere 2022-12-05 13:47:21 +01:00
parent 0953bc944d
commit cba3c270f5
4 changed files with 195 additions and 10 deletions

View file

@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
import org.matrix.android.sdk.internal.session.room.timeline.RoomSummaryEventDecryptor
import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
import timber.log.Timber
import javax.inject.Inject
@ -73,10 +74,9 @@ internal class RoomSummaryUpdater @Inject constructor(
@UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor,
// private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val roomSummaryEventDecryptor: RoomSummaryEventDecryptor
) {
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
@ -215,12 +215,7 @@ internal class RoomSummaryUpdater @Inject constructor(
Timber.v("Decryption skipped due to missing root event $eventId")
}
else -> {
if (root.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
Timber.v("Should decrypt $eventId")
tryOrNull {
runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") }
}?.let { root.setDecryptionResult(it) }
}
roomSummaryEventDecryptor.requestDecryption(root.asDomain())
}
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class RoomSummaryEventDecryptor @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
private val cryptoService: dagger.Lazy<CryptoService>
) {
internal sealed class Message {
data class DecryptEvent(val event: Event) : Message()
data class NewSessionImported(val sessionId: String) : Message()
}
private val scope: CoroutineScope = CoroutineScope(
cryptoCoroutineScope.coroutineContext
+ SupervisorJob()
+ CoroutineName("RoomSummaryDecryptor")
)
private val channel = Channel<Message>(capacity = 300)
private val newSessionListener = object : NewSessionListener {
override fun onNewSession(roomId: String?, sessionId: String) {
scope.launch(coroutineDispatchers.computation) {
channel.send(Message.NewSessionImported(sessionId))
}
}
}
private val unknownSessionsFailure = mutableMapOf<String, MutableSet<Event>>()
init {
scope.launch {
cryptoService.get().addNewSessionListener(newSessionListener)
for (request in channel) {
when (request) {
is Message.DecryptEvent -> handleDecryptEvent(request.event)
is Message.NewSessionImported -> handleNewSessionImported(request.sessionId)
}
}
}
}
private fun handleNewSessionImported(sessionId: String) {
unknownSessionsFailure[sessionId]
?.toList()
.orEmpty()
.also {
unknownSessionsFailure[sessionId]?.clear()
}.forEach {
// post a retry!
requestDecryption(it)
}
}
private suspend fun handleDecryptEvent(event: Event) {
if (event.getClearType() != EventType.ENCRYPTED) return
val algorithm = event.content?.get("algorithm") as? String
if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
try {
val result = cryptoService.get().decryptEvent(event, "")
// now let's persist the result in database
monarchy.writeAsync { realm ->
val eventEntity = EventEntity.where(realm, event.eventId.orEmpty()).findFirst()
eventEntity?.setDecryptionResult(result)
}
} catch (failure: Throwable) {
Timber.v(failure, "Failed to decrypt event ${event.eventId}")
// We don't need to get more details, just mark this session in failures
if (failure is MXCryptoError.Base) {
monarchy.writeAsync { realm ->
EventEntity.where(realm, eventId = event.eventId.orEmpty())
.findFirst()
?.let {
it.decryptionErrorCode = failure.errorType.name
it.decryptionErrorReason = failure.technicalMessage.takeIf { it.isNotEmpty() } ?: failure.detailedErrorDescription
}
}
if (failure.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
|| failure.errorType == MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX) {
(event.content["session_id"] as? String)?.let { sessionId ->
unknownSessionsFailure.getOrPut(sessionId) { mutableSetOf() }
.add(event)
}
}
}
}
}
fun requestDecryption(event: Event) {
channel.trySend(Message.DecryptEvent(event))
}
}

View file

@ -32,10 +32,12 @@ import javax.inject.Inject
class HomeFilteredRoomsController @Inject constructor(
private val roomSummaryItemFactory: RoomSummaryItemFactory,
fontScalePreferences: FontScalePreferences
fontScalePreferences: FontScalePreferences,
roomSummaryRoomListDiffCallback: RoomSummaryRoomListDiffCallback,
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
modelBuildingHandler = createUIHandler(),
itemDiffCallback = roomSummaryRoomListDiffCallback,
) {
private var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2022 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 im.vector.app.features.home.room.list.home
import androidx.recyclerview.widget.DiffUtil
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
class RoomSummaryRoomListDiffCallback @Inject constructor(
vectorPreferences: VectorPreferences
): DiffUtil.ItemCallback<RoomSummary>() {
override fun areItemsTheSame(oldItem: RoomSummary, newItem: RoomSummary): Boolean {
return oldItem.roomId == newItem.roomId
}
override fun areContentsTheSame(oldItem: RoomSummary, newItem: RoomSummary): Boolean {
// for this use case we can test less things
if (oldItem.roomId != newItem.roomId) return false
if (oldItem.displayName != newItem.displayName) return false
if (oldItem.name != newItem.name) return false
if (oldItem.topic != newItem.topic) return false
if (oldItem.avatarUrl != newItem.avatarUrl) return false
if (oldItem.canonicalAlias != newItem.canonicalAlias) return false
if (oldItem.aliases != newItem.aliases) return false
if (oldItem.isDirect != newItem.isDirect) return false
if (oldItem.directUserPresence != newItem.directUserPresence) return false
if (oldItem.latestPreviewableEvent != newItem.latestPreviewableEvent) return false
if (oldItem.notificationCount != newItem.notificationCount) return false
if (oldItem.highlightCount != newItem.highlightCount) return false
if (oldItem.threadNotificationCount != newItem.threadNotificationCount) return false
if (oldItem.threadHighlightCount != newItem.threadHighlightCount) return false
if (oldItem.hasUnreadMessages != newItem.hasUnreadMessages) return false
if (oldItem.userDrafts != newItem.userDrafts) return false
if (oldItem.isEncrypted != newItem.isEncrypted) return false
if (oldItem.typingUsers != newItem.typingUsers) return false
if (oldItem.hasFailedSending != newItem.hasFailedSending) return false
return true
}
}