Исправлены синхронизация групп, выделение сообщений и фон чата

This commit is contained in:
2026-03-07 23:43:09 +05:00
parent 364b166581
commit 85bddb798c
8 changed files with 352 additions and 32 deletions

View File

@@ -53,6 +53,36 @@ class AvatarRepository(
return false
}
}
private fun normalizeOwnerKey(publicKey: String): String {
val trimmed = publicKey.trim()
if (trimmed.isBlank()) return trimmed
return when {
trimmed.startsWith("#group:") -> {
val groupId = trimmed.removePrefix("#group:").trim()
if (groupId.isBlank()) trimmed else "#group:$groupId"
}
trimmed.startsWith("group:", ignoreCase = true) -> {
val groupId = trimmed.substringAfter(':').trim()
if (groupId.isBlank()) trimmed else "#group:$groupId"
}
else -> trimmed
}
}
private fun lookupKeys(publicKey: String): List<String> {
val normalized = normalizeOwnerKey(publicKey)
if (normalized.isBlank()) return emptyList()
val keys = linkedSetOf(normalized)
if (normalized.startsWith("#group:")) {
keys.add(normalized.removePrefix("#group:"))
}
val trimmed = publicKey.trim()
if (trimmed.isNotBlank()) {
keys.add(trimmed)
}
return keys.toList()
}
/**
* Получить аватары пользователя
@@ -60,14 +90,20 @@ class AvatarRepository(
* @param allDecode true = вся история, false = только последний (для списков)
*/
fun getAvatars(publicKey: String, allDecode: Boolean = false): StateFlow<List<AvatarInfo>> {
val normalizedKey = normalizeOwnerKey(publicKey)
val keys = lookupKeys(publicKey)
if (normalizedKey.isBlank() || keys.isEmpty()) {
return MutableStateFlow(emptyList<AvatarInfo>()).asStateFlow()
}
// Проверяем LRU cache (accessOrder=true обновляет позицию при get)
memoryCache[publicKey]?.let { return it.flow.asStateFlow() }
memoryCache[normalizedKey]?.let { return it.flow.asStateFlow() }
// Создаем новый flow для этого пользователя
val flow = MutableStateFlow<List<AvatarInfo>>(emptyList())
// Подписываемся на изменения в БД
val job = avatarDao.getAvatars(publicKey)
val job = avatarDao.getAvatarsByKeys(keys)
.onEach { entities ->
val avatars = if (allDecode) {
// Параллельная загрузка всей истории
@@ -86,7 +122,7 @@ class AvatarRepository(
}
.launchIn(repositoryScope)
memoryCache[publicKey] = CacheEntry(flow, job)
memoryCache[normalizedKey] = CacheEntry(flow, job)
return flow.asStateFlow()
}
@@ -94,7 +130,9 @@ class AvatarRepository(
* Получить последний аватар пользователя (suspend версия)
*/
suspend fun getLatestAvatar(publicKey: String): AvatarInfo? {
val entity = avatarDao.getLatestAvatar(publicKey) ?: return null
val keys = lookupKeys(publicKey)
if (keys.isEmpty()) return null
val entity = avatarDao.getLatestAvatarByKeys(keys) ?: return null
return loadAndDecryptAvatar(entity)
}
@@ -108,22 +146,24 @@ class AvatarRepository(
suspend fun saveAvatar(fromPublicKey: String, base64Image: String) {
withContext(Dispatchers.IO) {
try {
val ownerKey = normalizeOwnerKey(fromPublicKey)
if (ownerKey.isBlank()) return@withContext
// Сохраняем файл
val filePath = AvatarFileManager.saveAvatar(context, base64Image, fromPublicKey)
val filePath = AvatarFileManager.saveAvatar(context, base64Image, ownerKey)
// Сохраняем в БД
val entity = AvatarCacheEntity(
publicKey = fromPublicKey,
publicKey = ownerKey,
avatar = filePath,
timestamp = System.currentTimeMillis()
)
avatarDao.insertAvatar(entity)
// Очищаем старые аватары (оставляем только последние N)
avatarDao.deleteOldAvatars(fromPublicKey, MAX_AVATAR_HISTORY)
avatarDao.deleteOldAvatars(ownerKey, MAX_AVATAR_HISTORY)
// 🔄 Обновляем memory cache если он существует
val cached = memoryCache[fromPublicKey]
val cached = memoryCache[ownerKey]
if (cached != null) {
val avatarInfo = loadAndDecryptAvatar(entity)
if (avatarInfo != null) {