diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/MessageRepository.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/MessageRepository.kt index a39bf22..c2b6d3a 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/MessageRepository.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/MessageRepository.kt @@ -1,6 +1,7 @@ package ru.fromchat.api.local.db.store import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import ru.fromchat.api.ApiClient import ru.fromchat.api.local.messages.ChatListPreviewState import ru.fromchat.api.local.messages.ChatListPreviewStrings @@ -21,7 +22,9 @@ object MessageRepository { MessageCacheStore.observeMessages(activeInstance(), conversationId) fun observePublicMessages(): Flow> = - observeMessages(conversationIdForGroup(GENERAL_PUBLIC_GROUP_ID)) + observeMessages(conversationIdForGroup(GENERAL_PUBLIC_GROUP_ID)).map { rows -> + ProfileCache.enrichPublicMessagesForDisplay(rows) + } fun observeDmMessages(otherUserId: Int): Flow> = observeMessages(conversationIdForDm(otherUserId)) diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/ProfileCache.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/ProfileCache.kt index 952ce96..050731d 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/ProfileCache.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/api/local/db/store/ProfileCache.kt @@ -454,30 +454,33 @@ object ProfileCache { val uid = message.user_id if (uid <= 0) return 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) { val patched = existing.copy( verified = message.verified ?: existing.verified, 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) return } val uname = message.username.trim().ifBlank { existing?.username?.trim().orEmpty() } - val incomingDisplay = message.displayName?.trim()?.takeIf { it.isNotEmpty() } - ?: existing?.displayName?.takeIf { it.isNotBlank() } - if (uname.isBlank() && incomingDisplay.isNullOrBlank()) return - if (uname.isBlank() || incomingDisplay.isNullOrBlank()) { + val displayName = incomingDisplay ?: existing?.displayName?.takeIf { it.isNotBlank() } + if (uname.isBlank() && displayName.isNullOrBlank()) return + if (uname.isBlank() || displayName.isNullOrBlank()) { Logger.d( "ProfileCache", "mergePreviewFromPublicMessage missingIdentity id=$uid " + - "hasUsername=${uname.isNotBlank()} hasDisplayName=${!incomingDisplay.isNullOrBlank()}", + "hasUsername=${uname.isNotBlank()} hasDisplayName=${!displayName.isNullOrBlank()}", ) } val isDeleted = isDeletedPlaceholderUsername(uname) || existing?.deleted == true - val display = if (isDeleted) null else incomingDisplay - val pic = if (isDeleted) null else message.profile_picture?.takeIf { it.isNotBlank() } - ?: existing?.profilePicture + val display = if (isDeleted) null else displayName + val pic = if (isDeleted) null else incomingPic ?: existing?.profilePicture put( UserProfile( diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/panels/publicchat/PublicChatPanel.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/panels/publicchat/PublicChatPanel.kt index fc4e8d0..591d6dc 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/panels/publicchat/PublicChatPanel.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/panels/publicchat/PublicChatPanel.kt @@ -84,6 +84,7 @@ class PublicChatPanel( ProfileCache.enrichPublicMessageForDisplay( mergeMessageUiFields(fresh, message).copy( username = fresh.username, + displayName = fresh.displayName, profile_picture = fresh.profile_picture, verified = fresh.verified, verificationStatus = fresh.verificationStatus, @@ -179,6 +180,8 @@ class PublicChatPanel( } suspend fun hydrateFromLocalCache() { + // Sender display names live in ProfileCache (message rows only store userId). + runCatching { ProfileCache.hydrateFromDisk() } hydrateMessagesFromLocalCache() runCatching { PublicChatProfileCache.hydrateFromDisk() } PublicChatProfileCache.profile?.let { applyPublicChatProfile(it) } diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/MessageUiMerge.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/MessageUiMerge.kt index c820a44..03ac087 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/MessageUiMerge.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/MessageUiMerge.kt @@ -129,7 +129,17 @@ internal fun mergeMessageUiFields(db: Message, panel: Message?): Message { ?: db.pendingFileAspectRatio?.takeIf { it > 0f } ?: panel.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( + 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 { confirmed -> localPreview else -> panel.pendingFileUri ?: db.pendingFileUri