Skip to content

Commit 5caaaa4

Browse files
feat: Add caching to avatar
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent 8a0849e commit 5caaaa4

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

app/src/main/java/com/nextcloud/talk/profile/AvatarSection.kt

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
package com.nextcloud.talk.profile
88

9+
import androidx.compose.foundation.isSystemInDarkTheme
910
import androidx.compose.foundation.layout.Arrangement
1011
import androidx.compose.foundation.layout.Column
1112
import androidx.compose.foundation.layout.Row
@@ -20,6 +21,8 @@ import androidx.compose.material3.Icon
2021
import androidx.compose.material3.MaterialTheme
2122
import androidx.compose.material3.Text
2223
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.LaunchedEffect
25+
import androidx.compose.runtime.remember
2326
import androidx.compose.ui.Alignment
2427
import androidx.compose.ui.Modifier
2528
import androidx.compose.ui.draw.clip
@@ -30,20 +33,58 @@ import androidx.compose.ui.res.stringResource
3033
import androidx.compose.ui.text.style.TextOverflow
3134
import androidx.compose.ui.unit.Dp
3235
import androidx.compose.ui.unit.dp
36+
import coil.annotation.ExperimentalCoilApi
3337
import coil.compose.AsyncImage
38+
import coil.imageLoader
39+
import coil.request.CachePolicy
3440
import coil.request.ImageRequest
3541
import com.nextcloud.talk.R
3642
import com.nextcloud.talk.utils.ApiUtils
3743

44+
@OptIn(ExperimentalCoilApi::class)
3845
@Composable
3946
private fun AvatarImage(state: ProfileUiState, avatarSize: Dp) {
47+
val context = LocalContext.current
4048
val user = state.currentUser
41-
val url = ApiUtils.getUrlForAvatar(user?.baseUrl,state.currentUser?.userId, true)
42-
val model = ImageRequest.Builder(LocalContext.current)
43-
.data(url)
44-
.crossfade(true)
45-
.build()
46-
49+
val isDark = isSystemInDarkTheme()
50+
val url = ApiUtils.getUrlForAvatar(user?.baseUrl, user?.userId, requestBigSize = true, darkMode = isDark)
51+
val cachePolicy = if (state.avatarRefreshKey > 0) CachePolicy.WRITE_ONLY else CachePolicy.ENABLED
52+
val model = remember(url, state.avatarRefreshKey) {
53+
ImageRequest.Builder(context)
54+
.data(url)
55+
.memoryCachePolicy(cachePolicy)
56+
.diskCachePolicy(cachePolicy)
57+
.crossfade(true)
58+
.build()
59+
}
60+
LaunchedEffect(state.avatarRefreshKey) {
61+
if (state.avatarRefreshKey > 0) {
62+
val imageLoader = context.imageLoader
63+
// Evict both theme variants so a subsequent dark/light switch never shows
64+
// a stale avatar regardless of which theme was active during the upload.
65+
val urlLight = ApiUtils.getUrlForAvatar(user?.baseUrl, user?.userId, requestBigSize = true, darkMode = false)
66+
val urlDark = ApiUtils.getUrlForAvatar(user?.baseUrl, user?.userId, requestBigSize = true, darkMode = true)
67+
listOf(urlLight, urlDark).forEach { variantUrl ->
68+
imageLoader.memoryCache?.let { cache ->
69+
cache.keys.filter { it.key == variantUrl }.forEach { cache.remove(it) }
70+
}
71+
imageLoader.diskCache?.remove(variantUrl)
72+
}
73+
// Always prefetch the OTHER theme URL so a subsequent theme switch shows the correct
74+
// avatar immediately:
75+
// - Upload/camera/choose: server returns the same image for both themes.
76+
// - Delete: server returns theme-aware generated avatars that differ per theme,
77+
// so each must be fetched and cached independently.
78+
val otherUrl = if (isDark) urlLight else urlDark
79+
imageLoader.enqueue(
80+
ImageRequest.Builder(context)
81+
.data(otherUrl)
82+
.memoryCachePolicy(CachePolicy.WRITE_ONLY)
83+
.diskCachePolicy(CachePolicy.WRITE_ONLY)
84+
.build()
85+
)
86+
}
87+
}
4788
AsyncImage(
4889
model = model,
4990
contentDescription = stringResource(R.string.avatar),

app/src/main/java/com/nextcloud/talk/profile/ProfileActivity.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,8 @@ class ProfileActivity : BaseActivity() {
560560

561561
override fun onNext(genericOverall: GenericOverall) {
562562
profileUiState = profileUiState.copy(
563-
avatarRefreshKey = profileUiState.avatarRefreshKey + 1
563+
avatarRefreshKey = profileUiState.avatarRefreshKey + 1,
564+
avatarIsDeleted = true
564565
)
565566
}
566567

@@ -591,7 +592,8 @@ class ProfileActivity : BaseActivity() {
591592

592593
override fun onNext(genericOverall: GenericOverall) {
593594
profileUiState = profileUiState.copy(
594-
avatarRefreshKey = profileUiState.avatarRefreshKey + 1
595+
avatarRefreshKey = profileUiState.avatarRefreshKey + 1,
596+
avatarIsDeleted = false
595597
)
596598
}
597599

app/src/main/java/com/nextcloud/talk/profile/ProfileState.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ data class ProfileUiState(
2525
val displayName: String = "",
2626
val baseUrl: String = "",
2727
val currentUser: User? = null,
28-
/** Increment to tell the avatar AndroidView to reload (e.g. after upload/delete). */
28+
/** Increment to tell the avatar composable to reload (e.g. after upload/delete). */
2929
val avatarRefreshKey: Int = 0,
30+
/** True when the most recent avatar action was a delete (server-generated avatars differ per
31+
* theme); false for upload/choose/camera (same image for both themes). */
32+
val avatarIsDeleted: Boolean = false,
3033
val isEditMode: Boolean = false,
3134
val showAvatarButtons: Boolean = false,
3235
val showProfileEnabledCard: Boolean = false,

0 commit comments

Comments
 (0)