From 9c0fae385c25c332321409aae636053e3fe721b4 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sat, 10 Jan 2026 20:42:40 +0500 Subject: [PATCH] feat: Refactor MessageInputBar to improve emoji picker integration and UI responsiveness --- .../messenger/ui/chats/ChatDetailScreen.kt | 114 ++++++++++-------- 1 file changed, 65 insertions(+), 49 deletions(-) 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 a08be57..59e8abf 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 @@ -19,8 +19,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -107,7 +109,7 @@ fun ChatDetailScreen( // Кастомный TopAppBar для чата Surface( color = backgroundColor, - shadowElevation = 4.dp + shadowElevation = 0.dp ) { Row( modifier = Modifier @@ -209,7 +211,6 @@ fun ChatDetailScreen( modifier = Modifier .fillMaxSize() .padding(paddingValues) - .imePadding() // Весь контент поднимается с клавиатурой ) { // Список сообщений Box( @@ -407,29 +408,23 @@ private fun MessageInputBar( // Состояние эмодзи пикера var showEmojiPicker by remember { mutableStateOf(false) } val keyboardController = LocalSoftwareKeyboardController.current - - // Скрываем клавиатуру когда открыт эмодзи пикер - LaunchedEffect(showEmojiPicker) { - if (showEmojiPicker) { - keyboardController?.hide() - } - } + val focusManager = LocalFocusManager.current // Цвета для glass morphism эффекта val glassBackground = if (isDarkTheme) - Color(0xFF1A1A1A).copy(alpha = 0.85f) + Color(0xFF1A1A1A).copy(alpha = 0.95f) else - Color(0xFFFFFFFF).copy(alpha = 0.9f) + Color(0xFFFFFFFF).copy(alpha = 0.95f) val inputGlass = if (isDarkTheme) - Color(0xFF2C2C2E).copy(alpha = 0.7f) + Color(0xFF2C2C2E).copy(alpha = 0.8f) else - Color(0xFFF2F2F7).copy(alpha = 0.85f) + Color(0xFFF2F2F7).copy(alpha = 0.9f) val inputBorder = if (isDarkTheme) - Color(0xFFFFFFFF).copy(alpha = 0.15f) + Color(0xFFFFFFFF).copy(alpha = 0.12f) else - Color(0xFF000000).copy(alpha = 0.08f) + Color(0xFF000000).copy(alpha = 0.06f) val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) @@ -458,33 +453,11 @@ private fun MessageInputBar( ) Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .imePadding() // Только инпут поднимается с клавиатурой ) { - // Эмодзи пикер (показывается над инпутом) - AnimatedVisibility( - visible = showEmojiPicker, - enter = slideInVertically( - initialOffsetY = { it }, - animationSpec = spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessMediumLow - ) - ) + fadeIn(), - exit = slideOutVertically( - targetOffsetY = { it }, - animationSpec = tween(200) - ) + fadeOut() - ) { - EmojiPickerPanel( - isDarkTheme = isDarkTheme, - onEmojiSelected = { emoji -> - onValueChange(value + emoji) - }, - onClose = { showEmojiPicker = false } - ) - } - - // Основной контейнер + // Основной контейнер инпута Box( modifier = Modifier .fillMaxWidth() @@ -493,9 +466,7 @@ private fun MessageInputBar( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 8.dp) - .padding(bottom = 4.dp) - .navigationBarsPadding(), + .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.Bottom ) { // Единый стеклянный контейнер для всего инпута @@ -514,9 +485,9 @@ private fun MessageInputBar( .background( brush = Brush.verticalGradient( colors = listOf( - Color.White.copy(alpha = if (isDarkTheme) 0.06f else 0.4f), + Color.White.copy(alpha = if (isDarkTheme) 0.05f else 0.3f), Color.Transparent, - Color.Black.copy(alpha = if (isDarkTheme) 0.03f else 0.02f) + Color.Black.copy(alpha = if (isDarkTheme) 0.02f else 0.01f) ), startY = 0f, endY = 80f @@ -532,7 +503,14 @@ private fun MessageInputBar( ) { // Кнопка смайликов (слева внутри инпута) IconButton( - onClick = { showEmojiPicker = !showEmojiPicker }, + onClick = { + if (showEmojiPicker) { + showEmojiPicker = false + } else { + keyboardController?.hide() + showEmojiPicker = true + } + }, modifier = Modifier.size(36.dp) ) { Icon( @@ -547,7 +525,10 @@ private fun MessageInputBar( Box( modifier = Modifier .weight(1f) - .padding(vertical = 8.dp), + .padding(vertical = 8.dp) + .clickable { + showEmojiPicker = false + }, contentAlignment = Alignment.CenterStart ) { BasicTextField( @@ -558,7 +539,13 @@ private fun MessageInputBar( fontSize = 16.sp ), cursorBrush = SolidColor(PrimaryBlue), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { focusState -> + if (focusState.isFocused) { + showEmojiPicker = false + } + }, maxLines = 5, decorationBox = { innerTextField -> Box( @@ -645,6 +632,35 @@ private fun MessageInputBar( } } } + + // Эмодзи пикер (показывается под инпутом, заменяя клавиатуру) + AnimatedVisibility( + visible = showEmojiPicker, + enter = expandVertically( + expandFrom = Alignment.Top, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessMediumLow + ) + ) + fadeIn(animationSpec = tween(150)), + exit = shrinkVertically( + shrinkTowards = Alignment.Top, + animationSpec = tween(200) + ) + fadeOut(animationSpec = tween(100)) + ) { + EmojiPickerPanel( + isDarkTheme = isDarkTheme, + onEmojiSelected = { emoji -> + onValueChange(value + emoji) + }, + onClose = { showEmojiPicker = false } + ) + } + + // Spacer для navigation bar когда эмодзи пикер НЕ открыт + if (!showEmojiPicker) { + Spacer(modifier = Modifier.navigationBarsPadding()) + } } }