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) }
|
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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user