diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index b2c4c4f..ce34e49 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -260,8 +260,7 @@ fun ChatDetailScreen( // Состояние показа логов var showLogs by remember { mutableStateOf(false) } - // val debugLogs by ProtocolManager.debugLogs.collectAsState() - val debugLogs = remember { emptyList() } + 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 diff --git a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiPicker.kt b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiPicker.kt index 78de1a1..2966612 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiPicker.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiPicker.kt @@ -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()