Fix public chat avatars being blank until profile is opened

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-07-20 20:29:00 +03:00
Unverified
parent d350623f14
commit 032897b56d
4 changed files with 28 additions and 9 deletions
@@ -1,6 +1,7 @@
package ru.fromchat.api.local.db.store package ru.fromchat.api.local.db.store
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.local.messages.ChatListPreviewState import ru.fromchat.api.local.messages.ChatListPreviewState
import ru.fromchat.api.local.messages.ChatListPreviewStrings import ru.fromchat.api.local.messages.ChatListPreviewStrings
@@ -21,7 +22,9 @@ object MessageRepository {
MessageCacheStore.observeMessages(activeInstance(), conversationId) MessageCacheStore.observeMessages(activeInstance(), conversationId)
fun observePublicMessages(): Flow<List<Message>> = fun observePublicMessages(): Flow<List<Message>> =
observeMessages(conversationIdForGroup(GENERAL_PUBLIC_GROUP_ID)) observeMessages(conversationIdForGroup(GENERAL_PUBLIC_GROUP_ID)).map { rows ->
ProfileCache.enrichPublicMessagesForDisplay(rows)
}
fun observeDmMessages(otherUserId: Int): Flow<List<Message>> = fun observeDmMessages(otherUserId: Int): Flow<List<Message>> =
observeMessages(conversationIdForDm(otherUserId)) observeMessages(conversationIdForDm(otherUserId))
@@ -454,30 +454,33 @@ object ProfileCache {
val uid = message.user_id val uid = message.user_id
if (uid <= 0) return if (uid <= 0) return
val existing = get(uid) val existing = get(uid)
val incomingDisplay = message.displayName?.trim()?.takeIf { it.isNotEmpty() }
val incomingPic = message.profile_picture?.takeIf { it.isNotBlank() }
if (existing != null && !existing.isClientPreviewOnly) { if (existing != null && !existing.isClientPreviewOnly) {
val patched = existing.copy( val patched = existing.copy(
verified = message.verified ?: existing.verified, verified = message.verified ?: existing.verified,
verificationStatus = message.verificationStatus ?: existing.verificationStatus, verificationStatus = message.verificationStatus ?: existing.verificationStatus,
displayName = existing.displayName?.takeIf { it.isNotBlank() } ?: incomingDisplay,
profilePicture = existing.profilePicture?.takeIf { it.isNotBlank() } ?: incomingPic,
username = existing.username.trim().ifBlank { message.username.trim() },
) )
if (patched != existing) put(patched) if (patched != existing) put(patched)
return return
} }
val uname = message.username.trim().ifBlank { existing?.username?.trim().orEmpty() } val uname = message.username.trim().ifBlank { existing?.username?.trim().orEmpty() }
val incomingDisplay = message.displayName?.trim()?.takeIf { it.isNotEmpty() } val displayName = incomingDisplay ?: existing?.displayName?.takeIf { it.isNotBlank() }
?: existing?.displayName?.takeIf { it.isNotBlank() } if (uname.isBlank() && displayName.isNullOrBlank()) return
if (uname.isBlank() && incomingDisplay.isNullOrBlank()) return if (uname.isBlank() || displayName.isNullOrBlank()) {
if (uname.isBlank() || incomingDisplay.isNullOrBlank()) {
Logger.d( Logger.d(
"ProfileCache", "ProfileCache",
"mergePreviewFromPublicMessage missingIdentity id=$uid " + "mergePreviewFromPublicMessage missingIdentity id=$uid " +
"hasUsername=${uname.isNotBlank()} hasDisplayName=${!incomingDisplay.isNullOrBlank()}", "hasUsername=${uname.isNotBlank()} hasDisplayName=${!displayName.isNullOrBlank()}",
) )
} }
val isDeleted = isDeletedPlaceholderUsername(uname) || existing?.deleted == true val isDeleted = isDeletedPlaceholderUsername(uname) || existing?.deleted == true
val display = if (isDeleted) null else incomingDisplay val display = if (isDeleted) null else displayName
val pic = if (isDeleted) null else message.profile_picture?.takeIf { it.isNotBlank() } val pic = if (isDeleted) null else incomingPic ?: existing?.profilePicture
?: existing?.profilePicture
put( put(
UserProfile( UserProfile(
@@ -84,6 +84,7 @@ class PublicChatPanel(
ProfileCache.enrichPublicMessageForDisplay( ProfileCache.enrichPublicMessageForDisplay(
mergeMessageUiFields(fresh, message).copy( mergeMessageUiFields(fresh, message).copy(
username = fresh.username, username = fresh.username,
displayName = fresh.displayName,
profile_picture = fresh.profile_picture, profile_picture = fresh.profile_picture,
verified = fresh.verified, verified = fresh.verified,
verificationStatus = fresh.verificationStatus, verificationStatus = fresh.verificationStatus,
@@ -179,6 +180,8 @@ class PublicChatPanel(
} }
suspend fun hydrateFromLocalCache() { suspend fun hydrateFromLocalCache() {
// Sender display names live in ProfileCache (message rows only store userId).
runCatching { ProfileCache.hydrateFromDisk() }
hydrateMessagesFromLocalCache() hydrateMessagesFromLocalCache()
runCatching { PublicChatProfileCache.hydrateFromDisk() } runCatching { PublicChatProfileCache.hydrateFromDisk() }
PublicChatProfileCache.profile?.let { applyPublicChatProfile(it) } PublicChatProfileCache.profile?.let { applyPublicChatProfile(it) }
@@ -129,7 +129,17 @@ internal fun mergeMessageUiFields(db: Message, panel: Message?): Message {
?: db.pendingFileAspectRatio?.takeIf { it > 0f } ?: db.pendingFileAspectRatio?.takeIf { it > 0f }
?: panel.fileDimensions?.firstOrNull()?.let { (w, h) -> aspectRatioFromDimensionPair(w, h) } ?: panel.fileDimensions?.firstOrNull()?.let { (w, h) -> aspectRatioFromDimensionPair(w, h) }
?: db.fileDimensions?.firstOrNull()?.let { (w, h) -> aspectRatioFromDimensionPair(w, h) } ?: db.fileDimensions?.firstOrNull()?.let { (w, h) -> aspectRatioFromDimensionPair(w, h) }
// DB rows only store userId; sender identity is reconstructed from ProfileCache and can
// briefly be blank. Keep non-blank panel fields (e.g. from a network payload) so text
// avatars / names are not wiped on every SQLDelight emission.
val merged = db.copy( val merged = db.copy(
username = db.username.trim().ifBlank { panel.username.trim() },
displayName = db.displayName?.trim()?.takeIf { it.isNotEmpty() }
?: panel.displayName?.trim()?.takeIf { it.isNotEmpty() },
profile_picture = db.profile_picture?.takeIf { it.isNotBlank() }
?: panel.profile_picture?.takeIf { it.isNotBlank() },
verified = db.verified ?: panel.verified,
verificationStatus = db.verificationStatus ?: panel.verificationStatus,
pendingFileUri = when { pendingFileUri = when {
confirmed -> localPreview confirmed -> localPreview
else -> panel.pendingFileUri ?: db.pendingFileUri else -> panel.pendingFileUri ?: db.pendingFileUri