66 */
77package com.nextcloud.talk.profile
88
9+ import androidx.compose.foundation.isSystemInDarkTheme
910import androidx.compose.foundation.layout.Arrangement
1011import androidx.compose.foundation.layout.Column
1112import androidx.compose.foundation.layout.Row
@@ -20,6 +21,8 @@ import androidx.compose.material3.Icon
2021import androidx.compose.material3.MaterialTheme
2122import androidx.compose.material3.Text
2223import androidx.compose.runtime.Composable
24+ import androidx.compose.runtime.LaunchedEffect
25+ import androidx.compose.runtime.remember
2326import androidx.compose.ui.Alignment
2427import androidx.compose.ui.Modifier
2528import androidx.compose.ui.draw.clip
@@ -30,20 +33,58 @@ import androidx.compose.ui.res.stringResource
3033import androidx.compose.ui.text.style.TextOverflow
3134import androidx.compose.ui.unit.Dp
3235import androidx.compose.ui.unit.dp
36+ import coil.annotation.ExperimentalCoilApi
3337import coil.compose.AsyncImage
38+ import coil.imageLoader
39+ import coil.request.CachePolicy
3440import coil.request.ImageRequest
3541import com.nextcloud.talk.R
3642import com.nextcloud.talk.utils.ApiUtils
3743
44+ @OptIn(ExperimentalCoilApi ::class )
3845@Composable
3946private 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),
0 commit comments