feat: Implement emoji preloading in background for improved performance
This commit is contained in:
@@ -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 с эффектом блюра
|
||||
// Плоский контейнер как у инпута
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
// Border сверху
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(
|
||||
if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.7f)
|
||||
else Color(0xFFF8F9FA).copy(alpha = 0.8f)
|
||||
.height(0.5.dp)
|
||||
.background(if (isDarkTheme) Color.White.copy(alpha = 0.15f) else Color.Black.copy(alpha = 0.1f))
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.12f)
|
||||
else Color.Black.copy(alpha = 0.08f),
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
)
|
||||
) {
|
||||
|
||||
// Кнопки 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,23 +1636,27 @@ private fun MessageInputBar(
|
||||
.fillMaxWidth()
|
||||
.imePadding()
|
||||
) {
|
||||
// Если пользователь заблокирован - показываем BlockedChatFooter
|
||||
// Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
|
||||
if (isBlocked) {
|
||||
// BLOCKED CHAT FOOTER
|
||||
// BLOCKED CHAT FOOTER - плоский стиль
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
// 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)
|
||||
.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),
|
||||
.padding(bottom = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
@@ -1654,6 +1674,7 @@ private fun MessageInputBar(
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 🔥 TELEGRAM STYLE: фон как у чата, верхний border
|
||||
Column(
|
||||
|
||||
@@ -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
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user