diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/AvatarManager.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/AvatarManager.kt index 4975533b17011b67795ca3dff63e8135b792d5fc..9593ae47aedda85331c13e2dbed23838becaeafb 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/AvatarManager.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/AvatarManager.kt @@ -20,6 +20,7 @@ package org.briarproject.briar.desktop.attachment.media import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.res.loadImageBitmap @@ -31,6 +32,7 @@ import org.briarproject.briar.api.attachment.AttachmentHeader import org.briarproject.briar.api.attachment.AttachmentReader import org.briarproject.briar.api.identity.AuthorInfo import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.ui.LocalAvatarManager import javax.inject.Inject @@ -39,10 +41,15 @@ class AvatarManager @Inject constructor( private val executors: BriarExecutors, ) { - // access only on Dispatchers.Swing + @UiExecutor // access only on Dispatchers.Swing // TODO we may want to monitor cache size and evict cache entries again private val cache = HashMap<MessageId, ImageBitmap>() + @UiExecutor + fun getAvatarFromCache(attachmentHeader: AttachmentHeader): ImageBitmap? { + return cache[attachmentHeader.messageId] + } + suspend fun loadAvatar( attachmentHeader: AttachmentHeader, ): ImageBitmap = withContext(Dispatchers.Swing) { @@ -51,8 +58,12 @@ class AvatarManager @Inject constructor( executors.runOnDbThreadWithTransaction(true) { txn -> attachmentReader.getAttachment(txn, attachmentHeader).stream.use { inputStream -> loadImageBitmap(inputStream) + }.also { + txn.attach { + cache[attachmentHeader.messageId] = it + } } - }.also { cache[attachmentHeader.messageId] = it } + } } } @@ -68,6 +79,11 @@ fun AvatarProducer(authorInfo: AuthorInfo): State<ImageBitmap?>? { null } else { val avatarManager = checkNotNull(LocalAvatarManager.current) + // if avatar is cached, return it directly to avoid recomposition with produceState + avatarManager.getAvatarFromCache(avatarHeader)?.let { + return mutableStateOf(it) + } + // avatar is not cached, so load it produceState<ImageBitmap?>(null, avatarHeader.messageId) { value = avatarManager.loadAvatar(avatarHeader) }