Refactor and optimize various components
- Updated RosettaFirebaseMessagingService to use IO dispatcher for blocking calls. - Enhanced AvatarRepository with LRU caching and improved coroutine handling for avatar loading. - Implemented timeout for websocket connection in UnlockScreen. - Added selection mode functionality in ChatsListScreen with haptic feedback and improved UI for chat actions. - Improved animated dots in AttachmentComponents for a smoother visual effect. - Refactored image downloading and caching logic in ChatDetailComponents to streamline the process. - Optimized SwipeBackContainer to simplify gesture handling. - Adjusted swipe back behavior in OtherProfileScreen based on image viewer state.
This commit is contained in:
@@ -5,8 +5,13 @@ import com.rosetta.messenger.database.AvatarCacheEntity
|
||||
import com.rosetta.messenger.database.AvatarDao
|
||||
import com.rosetta.messenger.utils.AvatarFileManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* Репозиторий для работы с аватарами
|
||||
@@ -23,20 +28,31 @@ class AvatarRepository(
|
||||
private val context: Context,
|
||||
private val avatarDao: AvatarDao,
|
||||
private val currentPublicKey: String
|
||||
) {
|
||||
) : Closeable {
|
||||
companion object {
|
||||
private const val TAG = "AvatarRepository"
|
||||
private const val MAX_AVATAR_HISTORY = 5 // Хранить последние N аватаров
|
||||
private const val MAX_CACHE_SIZE = 100
|
||||
}
|
||||
|
||||
|
||||
// Repository scope для coroutines
|
||||
private val supervisorJob = kotlinx.coroutines.SupervisorJob()
|
||||
private val repositoryScope = kotlinx.coroutines.CoroutineScope(
|
||||
kotlinx.coroutines.SupervisorJob() + Dispatchers.IO
|
||||
supervisorJob + Dispatchers.IO
|
||||
)
|
||||
|
||||
// In-memory cache (как decodedAvatarsCache в desktop)
|
||||
// publicKey -> Flow<List<AvatarInfo>>
|
||||
private val memoryCache = mutableMapOf<String, MutableStateFlow<List<AvatarInfo>>>()
|
||||
|
||||
// In-memory LRU cache: publicKey -> (Flow, Job)
|
||||
// При вытеснении отменяем Job подписки на БД
|
||||
private data class CacheEntry(val flow: MutableStateFlow<List<AvatarInfo>>, val job: Job?)
|
||||
private val memoryCache = object : LinkedHashMap<String, CacheEntry>(MAX_CACHE_SIZE, 0.75f, true) {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, CacheEntry>?): Boolean {
|
||||
if (size > MAX_CACHE_SIZE) {
|
||||
eldest?.value?.job?.cancel()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить аватары пользователя
|
||||
@@ -44,22 +60,21 @@ class AvatarRepository(
|
||||
* @param allDecode true = вся история, false = только последний (для списков)
|
||||
*/
|
||||
fun getAvatars(publicKey: String, allDecode: Boolean = false): StateFlow<List<AvatarInfo>> {
|
||||
// Проверяем memory cache
|
||||
if (memoryCache.containsKey(publicKey)) {
|
||||
return memoryCache[publicKey]!!.asStateFlow()
|
||||
}
|
||||
|
||||
// Проверяем LRU cache (accessOrder=true обновляет позицию при get)
|
||||
memoryCache[publicKey]?.let { return it.flow.asStateFlow() }
|
||||
|
||||
// Создаем новый flow для этого пользователя
|
||||
val flow = MutableStateFlow<List<AvatarInfo>>(emptyList())
|
||||
memoryCache[publicKey] = flow
|
||||
|
||||
// Подписываемся на изменения в БД с использованием repository scope
|
||||
avatarDao.getAvatars(publicKey)
|
||||
|
||||
// Подписываемся на изменения в БД
|
||||
val job = avatarDao.getAvatars(publicKey)
|
||||
.onEach { entities ->
|
||||
val avatars = if (allDecode) {
|
||||
// Загружаем всю историю
|
||||
entities.mapNotNull { entity ->
|
||||
loadAndDecryptAvatar(entity)
|
||||
// Параллельная загрузка всей истории
|
||||
coroutineScope {
|
||||
entities.map { entity -> async { loadAndDecryptAvatar(entity) } }
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
}
|
||||
} else {
|
||||
// Загружаем только последний
|
||||
@@ -70,7 +85,8 @@ class AvatarRepository(
|
||||
flow.value = avatars
|
||||
}
|
||||
.launchIn(repositoryScope)
|
||||
|
||||
|
||||
memoryCache[publicKey] = CacheEntry(flow, job)
|
||||
return flow.asStateFlow()
|
||||
}
|
||||
|
||||
@@ -107,13 +123,12 @@ class AvatarRepository(
|
||||
avatarDao.deleteOldAvatars(fromPublicKey, MAX_AVATAR_HISTORY)
|
||||
|
||||
// 🔄 Обновляем memory cache если он существует
|
||||
val cachedFlow = memoryCache[fromPublicKey]
|
||||
if (cachedFlow != null) {
|
||||
val cached = memoryCache[fromPublicKey]
|
||||
if (cached != null) {
|
||||
val avatarInfo = loadAndDecryptAvatar(entity)
|
||||
if (avatarInfo != null) {
|
||||
cachedFlow.value = listOf(avatarInfo)
|
||||
cached.flow.value = listOf(avatarInfo)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -172,8 +187,8 @@ class AvatarRepository(
|
||||
// Удаляем из БД
|
||||
avatarDao.deleteAllAvatars(currentPublicKey)
|
||||
|
||||
// Очищаем memory cache
|
||||
memoryCache.remove(currentPublicKey)
|
||||
// Очищаем memory cache + отменяем Job
|
||||
memoryCache.remove(currentPublicKey)?.job?.cancel()
|
||||
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
@@ -206,8 +221,14 @@ class AvatarRepository(
|
||||
* Очистить memory cache (для освобождения памяти)
|
||||
*/
|
||||
fun clearMemoryCache() {
|
||||
memoryCache.values.forEach { it.job?.cancel() }
|
||||
memoryCache.clear()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
clearMemoryCache()
|
||||
supervisorJob.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Предзагрузить системные аватары (для ботов/системных аккаунтов)
|
||||
@@ -216,14 +237,14 @@ class AvatarRepository(
|
||||
suspend fun preloadSystemAvatars(systemAccounts: Map<String, String>) {
|
||||
withContext(Dispatchers.IO) {
|
||||
systemAccounts.forEach { (publicKey, base64Avatar) ->
|
||||
// Сохраняем только в memory cache, не в БД
|
||||
// Сохраняем только в memory cache, не в БД (job=null — нет подписки)
|
||||
val flow = MutableStateFlow(listOf(
|
||||
AvatarInfo(
|
||||
base64Data = base64Avatar,
|
||||
timestamp = 0
|
||||
)
|
||||
))
|
||||
memoryCache[publicKey] = flow
|
||||
memoryCache[publicKey] = CacheEntry(flow, job = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user