From dda07d80af27e07a685f281586d7238baeceee22 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 18:48:53 +0500 Subject: [PATCH] feat: Implement Telegram-style emoji picker with dynamic height and improved user experience --- .../messenger/ui/chats/ChatDetailScreen.kt | 136 +++++++++++++----- 1 file changed, 104 insertions(+), 32 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 2ff5cc6..28a0681 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 @@ -12,6 +12,8 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed @@ -41,6 +43,7 @@ import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.font.FontFamily @@ -1515,8 +1518,6 @@ private fun MessageInputBar( ) { val hasReply = replyMessages.isNotEmpty() var showEmojiPicker by remember { mutableStateOf(false) } - // Флаг для запуска закрытия клавиатуры перед открытием emoji picker - var pendingEmojiPicker by remember { mutableStateOf(false) } val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current val interactionSource = remember { MutableInteractionSource() } @@ -1525,43 +1526,37 @@ private fun MessageInputBar( // Получаем context и view для гарантированного закрытия клавиатуры val context = LocalContext.current val view = LocalView.current + val density = LocalDensity.current + + // 🔥 Отслеживаем высоту клавиатуры (Telegram-style) + val imeInsets = WindowInsets.ime + val imeHeight = with(density) { imeInsets.getBottom(density).toDp() } + val isKeyboardVisible = imeHeight > 0.dp + + // Высота панели эмодзи = высота клавиатуры (или 280dp по умолчанию) + val emojiPanelHeight = if (imeHeight > 50.dp) imeHeight else 280.dp // Состояние отправки val canSend = remember(value) { value.isNotBlank() } - // Easing анимации - val backEasing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1f) - // Функция для гарантированного закрытия клавиатуры через InputMethodManager fun hideKeyboardCompletely() { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(view.windowToken, 0) focusManager.clearFocus(force = true) } - - // Эффект для закрытия клавиатуры и открытия emoji picker - LaunchedEffect(pendingEmojiPicker) { - if (pendingEmojiPicker) { - // Гарантированно закрываем клавиатуру через InputMethodManager - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.windowToken, 0) - focusManager.clearFocus(force = true) - // Ждём пока клавиатура закроется - delay(200) - // Теперь открываем emoji picker - showEmojiPicker = true - pendingEmojiPicker = false - } - } - // Функция переключения emoji picker + // 🔥 Функция переключения emoji picker - TELEGRAM STYLE (без прыжков) fun toggleEmojiPicker() { if (showEmojiPicker) { - // Закрываем emoji picker + // Закрываем emoji picker и открываем клавиатуру showEmojiPicker = false + // Клавиатура откроется автоматически т.к. фокус остался } else { - // Запускаем процесс: сначала закрыть клавиатуру, потом открыть picker - pendingEmojiPicker = true + // Закрываем клавиатуру но оставляем фокус + keyboardController?.hide() + // Сразу показываем emoji panel (высота та же - нет прыжка) + showEmojiPicker = true } } @@ -1765,18 +1760,95 @@ private fun MessageInputBar( } } // End of else (not blocked) - // Apple Emoji Picker - только показываем если не заблокирован + // 🔥 TELEGRAM-STYLE EMOJI PICKER - фиксированная высота как у клавиатуры if (!isBlocked) { + // Показываем когда showEmojiPicker = true ИЛИ когда клавиатура закрывается + // Это предотвращает прыжок инпута + val shouldShowEmojiSpace = showEmojiPicker || (isKeyboardVisible && showEmojiPicker) + AnimatedVisibility( visible = showEmojiPicker, - enter = expandVertically(expandFrom = Alignment.Bottom) + fadeIn(), - exit = shrinkVertically(shrinkTowards = Alignment.Bottom) + fadeOut() + enter = fadeIn(tween(150)), + exit = fadeOut(tween(100)) ) { - AppleEmojiPickerPanel( - isDarkTheme = isDarkTheme, - onEmojiSelected = { emoji -> onValueChange(value + emoji) }, - onClose = { showEmojiPicker = false } - ) + // Telegram-style simple emoji grid - высота = высота клавиатуры + Column( + modifier = Modifier + .fillMaxWidth() + .height(emojiPanelHeight) // 🔥 Динамическая высота + .background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7)) + ) { + // Топ бар с кнопкой закрытия (как в Telegram) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = { + showEmojiPicker = false + } + ) { + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = "Close", + tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93) + ) + } + } + + // Простая сетка эмодзи (самые популярные) + val emojis = remember { + listOf( + "😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", + "🙂", "🙃", "😉", "😊", "😇", "🥰", "😍", "🤩", + "😘", "😗", "😚", "😙", "😋", "😛", "😜", "🤪", + "😝", "🤑", "🤗", "🤭", "🤫", "🤔", "🤐", "🤨", + "😐", "😑", "😶", "😏", "😒", "🙄", "😬", "🤥", + "😌", "😔", "😪", "🤤", "😴", "😷", "🤒", "🤕", + "🤢", "🤮", "🤧", "🥵", "🥶", "😶", "😵", "🤯", + "🤠", "🥳", "😎", "🤓", "🧐", "😕", "😟", "🙁", + "☹️", "😮", "😯", "😲", "😳", "🥺", "😦", "😧", + "😨", "😰", "😥", "😢", "😭", "😱", "😖", "😣", + "😞", "😓", "😩", "😫", "🥱", "😤", "😡", "😠", + "🤬", "😈", "👿", "💀", "☠️", "💩", "🤡", "👹", + "👺", "👻", "👽", "👾", "🤖", "😺", "😸", "😹", + "😻", "😼", "😽", "🙀", "😿", "😾", "❤️", "🧡", + "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔", + "❣️", "💕", "💞", "💓", "💗", "💖", "💘", "💝", + "👋", "🤚", "🖐", "✋", "🖖", "👌", "🤏", "✌️", + "🤞", "🤟", "🤘", "🤙", "👈", "👉", "👆", "🖕", + "👇", "☝️", "👍", "👎", "✊", "👊", "🤛", "🤜", + "👏", "🙌", "👐", "🤲", "🤝", "🙏", "✍️", "💅" + ) + } + + LazyVerticalGrid( + columns = GridCells.Fixed(8), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 4.dp), + contentPadding = PaddingValues(vertical = 8.dp) + ) { + items(emojis) { emoji -> + Box( + modifier = Modifier + .aspectRatio(1f) + .clickable { + onValueChange(value + emoji) + } + .padding(2.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = emoji, + fontSize = 28.sp + ) + } + } + } + } } } // End of if (!isBlocked) for emoji picker }