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) } var showLogs by remember { mutableStateOf(false) }
// val debugLogs by ProtocolManager.debugLogs.collectAsState() val debugLogs by com.rosetta.messenger.network.ProtocolManager.debugLogs.collectAsState()
val debugLogs = remember { emptyList<String>() }
// Состояние выпадающего меню // Состояние выпадающего меню
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
@@ -345,6 +344,8 @@ fun ChatDetailScreen(
if (!isSavedMessages) { if (!isSavedMessages) {
viewModel.subscribeToOnlineStatus() 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 (появляется при выборе сообщений) // 🔥 SELECTION ACTION BAR - Reply/Forward (появляется при выборе сообщений)
// Стеклянный стиль как у инпута с блюром // Плоский стиль как у инпута с border сверху
AnimatedVisibility( AnimatedVisibility(
visible = isSelectionMode, visible = isSelectionMode,
enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }), enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }),
@@ -949,46 +974,36 @@ fun ChatDetailScreen(
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.fillMaxWidth() .fillMaxWidth()
.imePadding() .imePadding()
.padding(horizontal = 16.dp, vertical = 12.dp)
) { ) {
// Glass container с эффектом блюра // Плоский контейнер как у инпута
Box( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(24.dp)) .background(backgroundColor)
.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)
)
) { ) {
// 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( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// Reply button - стеклянная кнопка с блюром // Reply button
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(12.dp))
.background( .background(PrimaryBlue.copy(alpha = 0.1f))
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)
)
.clickable { .clickable {
val selectedMsgs = messages val selectedMsgs = messages
.filter { selectedMessages.contains(it.id) } .filter { selectedMessages.contains(it.id) }
@@ -996,7 +1011,7 @@ fun ChatDetailScreen(
viewModel.setReplyMessages(selectedMsgs) viewModel.setReplyMessages(selectedMsgs)
selectedMessages = emptySet() selectedMessages = emptySet()
} }
.padding(vertical = 14.dp, horizontal = 16.dp), .padding(vertical = 14.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Row( Row(
@@ -1019,21 +1034,12 @@ fun ChatDetailScreen(
} }
} }
// Forward button - стеклянная кнопка с блюром // Forward button
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(12.dp))
.background( .background(PrimaryBlue.copy(alpha = 0.1f))
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)
)
.clickable { .clickable {
val selectedMsgs = messages val selectedMsgs = messages
.filter { selectedMessages.contains(it.id) } .filter { selectedMessages.contains(it.id) }
@@ -1041,7 +1047,7 @@ fun ChatDetailScreen(
viewModel.setForwardMessages(selectedMsgs) viewModel.setForwardMessages(selectedMsgs)
selectedMessages = emptySet() selectedMessages = emptySet()
} }
.padding(vertical = 14.dp, horizontal = 16.dp), .padding(vertical = 14.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Row( Row(
@@ -1074,27 +1080,37 @@ fun ChatDetailScreen(
if (showLogs) { if (showLogs) {
AlertDialog( AlertDialog(
onDismissRequest = { showLogs = false }, onDismissRequest = { showLogs = false },
containerColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
title = { title = {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Debug Logs", fontWeight = FontWeight.Bold) Text("Debug Logs (${debugLogs.size})", fontWeight = FontWeight.Bold, color = textColor)
IconButton( IconButton(
onClick = { 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 = { text = {
LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 400.dp)) { LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 500.dp)) {
items(debugLogs.reversed()) { log -> 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(
text = log, text = log,
fontSize = 12.sp, fontSize = 11.sp,
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
color = logColor,
modifier = Modifier.padding(vertical = 2.dp) modifier = Modifier.padding(vertical = 2.dp)
) )
} }
@@ -1102,14 +1118,14 @@ fun ChatDetailScreen(
item { item {
Text( Text(
text = "No logs yet. Try sending a message.", text = "No logs yet. Try sending a message.",
color = Color.Gray, color = secondaryTextColor,
fontSize = 12.sp 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() .fillMaxWidth()
.imePadding() .imePadding()
) { ) {
// Если пользователь заблокирован - показываем BlockedChatFooter // Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
if (isBlocked) { if (isBlocked) {
// BLOCKED CHAT FOOTER // BLOCKED CHAT FOOTER - плоский стиль
Row( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp) .background(backgroundColor)
.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
) { ) {
Icon( // Border сверху
Icons.Default.Block, Box(
contentDescription = null, modifier = Modifier
tint = Color(0xFFFF6B6B), .fillMaxWidth()
modifier = Modifier.size(20.dp) .height(0.5.dp)
) .background(if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f))
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
) )
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 { } else {
// 🔥 TELEGRAM STYLE: фон как у чата, верхний border // 🔥 TELEGRAM STYLE: фон как у чата, верхний border

View File

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