feat: Implement emoji preloading in background for improved performance

This commit is contained in:
k1ngsterr1
2026-01-13 03:32:18 +05:00
parent fa2fc98ca0
commit 62093e1b1e
2 changed files with 121 additions and 89 deletions

View File

@@ -260,8 +260,7 @@ fun ChatDetailScreen(
// Состояние показа логов
var showLogs by remember { mutableStateOf(false) }
// val debugLogs by ProtocolManager.debugLogs.collectAsState()
val debugLogs = remember { emptyList<String>() }
val debugLogs by com.rosetta.messenger.network.ProtocolManager.debugLogs.collectAsState()
// Состояние выпадающего меню
var showMenu by remember { mutableStateOf(false) }
@@ -345,6 +344,8 @@ fun ChatDetailScreen(
if (!isSavedMessages) {
viewModel.subscribeToOnlineStatus()
}
// 🔥 Предзагружаем эмодзи в фоне
com.rosetta.messenger.ui.components.EmojiCache.preload(context)
}
// Отмечаем сообщения как прочитанные когда они видны
@@ -658,6 +659,30 @@ fun ChatDetailScreen(
}
)
}
// Debug Logs
DropdownMenuItem(
text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
Icons.Default.BugReport,
contentDescription = null,
tint = secondaryTextColor,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
"Debug Logs",
color = textColor,
fontSize = 16.sp
)
}
},
onClick = {
showMenu = false
showLogs = true
}
)
}
}
}
@@ -940,7 +965,7 @@ fun ChatDetailScreen(
}
// 🔥 SELECTION ACTION BAR - Reply/Forward (появляется при выборе сообщений)
// Стеклянный стиль как у инпута с блюром
// Плоский стиль как у инпута с border сверху
AnimatedVisibility(
visible = isSelectionMode,
enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }),
@@ -949,46 +974,36 @@ fun ChatDetailScreen(
.align(Alignment.BottomCenter)
.fillMaxWidth()
.imePadding()
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
// Glass container с эффектом блюра
Box(
// Плоский контейнер как у инпута
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(
if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.7f)
else Color(0xFFF8F9FA).copy(alpha = 0.8f)
)
.border(
width = 1.dp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.12f)
else Color.Black.copy(alpha = 0.08f),
shape = RoundedCornerShape(24.dp)
)
.background(backgroundColor)
) {
// Border сверху
Box(
modifier = Modifier
.fillMaxWidth()
.height(0.5.dp)
.background(if (isDarkTheme) Color.White.copy(alpha = 0.15f) else Color.Black.copy(alpha = 0.1f))
)
// Кнопки Reply и Forward
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp, horizontal = 12.dp),
.padding(horizontal = 16.dp, vertical = 12.dp)
.padding(bottom = if (WindowInsets.ime.getBottom(LocalDensity.current) == 0) 16.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Reply button - стеклянная кнопка с блюром
// Reply button
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(
if (isDarkTheme) Color.White.copy(alpha = 0.1f)
else Color.Black.copy(alpha = 0.06f)
)
.border(
width = 0.5.dp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.15f)
else Color.Black.copy(alpha = 0.1f),
shape = RoundedCornerShape(16.dp)
)
.clip(RoundedCornerShape(12.dp))
.background(PrimaryBlue.copy(alpha = 0.1f))
.clickable {
val selectedMsgs = messages
.filter { selectedMessages.contains(it.id) }
@@ -996,7 +1011,7 @@ fun ChatDetailScreen(
viewModel.setReplyMessages(selectedMsgs)
selectedMessages = emptySet()
}
.padding(vertical = 14.dp, horizontal = 16.dp),
.padding(vertical = 14.dp),
contentAlignment = Alignment.Center
) {
Row(
@@ -1019,21 +1034,12 @@ fun ChatDetailScreen(
}
}
// Forward button - стеклянная кнопка с блюром
// Forward button
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(
if (isDarkTheme) Color.White.copy(alpha = 0.1f)
else Color.Black.copy(alpha = 0.06f)
)
.border(
width = 0.5.dp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.15f)
else Color.Black.copy(alpha = 0.1f),
shape = RoundedCornerShape(16.dp)
)
.clip(RoundedCornerShape(12.dp))
.background(PrimaryBlue.copy(alpha = 0.1f))
.clickable {
val selectedMsgs = messages
.filter { selectedMessages.contains(it.id) }
@@ -1041,7 +1047,7 @@ fun ChatDetailScreen(
viewModel.setForwardMessages(selectedMsgs)
selectedMessages = emptySet()
}
.padding(vertical = 14.dp, horizontal = 16.dp),
.padding(vertical = 14.dp),
contentAlignment = Alignment.Center
) {
Row(
@@ -1074,27 +1080,37 @@ fun ChatDetailScreen(
if (showLogs) {
AlertDialog(
onDismissRequest = { showLogs = false },
containerColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text("Debug Logs", fontWeight = FontWeight.Bold)
Text("Debug Logs (${debugLogs.size})", fontWeight = FontWeight.Bold, color = textColor)
IconButton(
onClick = {
// ProtocolManager.clearLogs()
com.rosetta.messenger.network.ProtocolManager.clearLogs()
}
) { Icon(Icons.Default.Delete, contentDescription = "Clear") }
) { Icon(Icons.Default.Delete, contentDescription = "Clear", tint = Color(0xFFFF3B30)) }
}
},
text = {
LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 400.dp)) {
LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 500.dp)) {
items(debugLogs.reversed()) { log ->
val logColor = when {
log.contains("") || log.contains("Error") -> Color(0xFFFF3B30)
log.contains("") -> Color(0xFF38B24D)
log.contains("📤") -> PrimaryBlue
log.contains("📥") -> Color(0xFFFF9500)
log.contains("⚠️") -> Color(0xFFFFCC00)
else -> if (isDarkTheme) Color.White.copy(alpha = 0.8f) else Color.Black.copy(alpha = 0.8f)
}
Text(
text = log,
fontSize = 12.sp,
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = logColor,
modifier = Modifier.padding(vertical = 2.dp)
)
}
@@ -1102,14 +1118,14 @@ fun ChatDetailScreen(
item {
Text(
text = "No logs yet. Try sending a message.",
color = Color.Gray,
color = secondaryTextColor,
fontSize = 12.sp
)
}
}
}
},
confirmButton = { TextButton(onClick = { showLogs = false }) { Text("Close") } }
confirmButton = { TextButton(onClick = { showLogs = false }) { Text("Close", color = PrimaryBlue) } }
)
}
@@ -1620,39 +1636,44 @@ private fun MessageInputBar(
.fillMaxWidth()
.imePadding()
) {
// Если пользователь заблокирован - показываем BlockedChatFooter
// Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
if (isBlocked) {
// BLOCKED CHAT FOOTER
Row(
// BLOCKED CHAT FOOTER - плоский стиль
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
.background(
color = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F3F5),
shape = RoundedCornerShape(12.dp)
)
.border(
width = 1.dp,
color = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE5E5EA),
shape = RoundedCornerShape(12.dp)
)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
.background(backgroundColor)
) {
Icon(
Icons.Default.Block,
contentDescription = null,
tint = Color(0xFFFF6B6B),
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "You need to unblock user to send messages.",
fontSize = 14.sp,
color = secondaryTextColor,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
// Border сверху
Box(
modifier = Modifier
.fillMaxWidth()
.height(0.5.dp)
.background(if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f))
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
.padding(bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
Icons.Default.Block,
contentDescription = null,
tint = Color(0xFFFF6B6B),
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "You need to unblock user to send messages.",
fontSize = 14.sp,
color = secondaryTextColor,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
}
} else {
// 🔥 TELEGRAM STYLE: фон как у чата, верхний border

View File

@@ -35,6 +35,7 @@ import coil.request.CachePolicy
import coil.request.ImageRequest
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
@@ -148,12 +149,23 @@ object EmojiCache {
var isLoaded by mutableStateOf(false)
private set
// Предзагрузка при старте приложения (вызывать из Application или MainActivity)
fun preload(context: Context) {
if (allEmojis != null) return
kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
loadEmojisInternal(context)
}
}
suspend fun loadEmojis(context: Context) {
if (allEmojis != null) {
isLoaded = true
return
}
loadEmojisInternal(context)
}
private suspend fun loadEmojisInternal(context: Context) {
withContext(Dispatchers.IO) {
try {
val emojis = context.assets.list("emoji")
@@ -341,20 +353,19 @@ fun AppleEmojiPickerPanel(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) } // "All" по умолчанию
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
val gridState = rememberLazyGridState()
var shouldLoad by remember { mutableStateOf(false) }
// Отложенная загрузка эмодзи (чтобы не блокировать UI при открытии)
// Загружаем эмодзи если еще не загружены (без задержки если уже в кеше)
LaunchedEffect(Unit) {
kotlinx.coroutines.delay(100) // Задержка 100ms для плавности
shouldLoad = true
EmojiCache.loadEmojis(context)
if (!EmojiCache.isLoaded) {
EmojiCache.loadEmojis(context)
}
}
// Текущие эмодзи для выбранной категории
val currentEmojis = remember(selectedCategory.key, EmojiCache.isLoaded, shouldLoad) {
if (shouldLoad && EmojiCache.isLoaded) {
val currentEmojis = remember(selectedCategory.key, EmojiCache.isLoaded) {
if (EmojiCache.isLoaded) {
EmojiCache.getEmojisForCategory(selectedCategory.key)
} else {
emptyList()
@@ -403,7 +414,7 @@ fun AppleEmojiPickerPanel(
)
// Сетка эмодзи
if (!shouldLoad || !EmojiCache.isLoaded) {
if (!EmojiCache.isLoaded) {
Box(
modifier = Modifier
.fillMaxWidth()