From 8c5efae0a134c2b5a8459dbacd311d564dee2514 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Wed, 14 Jan 2026 02:32:33 +0500 Subject: [PATCH] feat: Enhance emoji picker animation with smooth slide-up effect and visibility control --- .../messenger/ui/chats/ChatDetailScreen.kt | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 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 ffd9af7..55306dc 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 @@ -1039,17 +1039,13 @@ fun ChatDetailScreen( } } ) { paddingValues -> - // 🔥 Дополнительный отступ снизу для эмодзи панели - // Учитываем высоту панели + 16dp отступ снизу для безопасной зоны - val emojiBottomPadding = if (showEmojiPicker && imeHeight < 50.dp) (emojiPanelHeight + 16.dp) else 0.dp - - // 🔥 Column структура - список сжимается когда клавиатура/эмодзи открывается + // 🔥 Column структура - список сжимается когда клавиатура открывается + // Emoji panel внутри MessageInputBar - НЕ нужен дополнительный отступ Column( modifier = Modifier .fillMaxSize() .padding(top = paddingValues.calculateTopPadding()) .imePadding() // 🔥 Поднимаем весь контент над клавиатурой - .padding(bottom = emojiBottomPadding) // 🔥 Отступ для эмодзи панели (после imePadding!) .background(backgroundColor) ) { // Список сообщений - занимает всё доступное место @@ -2021,9 +2017,12 @@ private fun MessageInputBar( // Состояние отправки - можно отправить если есть текст ИЛИ есть reply val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply } - // 🔥 Закрываем эмодзи панель когда клавиатура открывается + // 🔥 Плавно закрываем эмодзи панель когда клавиатура ПОЛНОСТЬЮ открылась + // Используем LaunchedEffect с debounce чтобы избежать дёрганья LaunchedEffect(isKeyboardVisible) { if (isKeyboardVisible && showEmojiPicker) { + // Ждём пока клавиатура полностью поднимется (200ms) + kotlinx.coroutines.delay(200) onToggleEmojiPicker(false) } } @@ -2049,14 +2048,18 @@ private fun MessageInputBar( if (showEmojiPicker) { // Закрываем emoji picker и открываем клавиатуру onToggleEmojiPicker(false) - // Клавиатура откроется автоматически т.к. фокус остался + // Открываем клавиатуру + editTextView?.let { editText -> + editText.requestFocus() + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + } } else { - // Закрываем клавиатуру через IMM + // 🔥 Сначала показываем эмодзи панель + onToggleEmojiPicker(true) + // Потом скрываем клавиатуру (панель уже видна, поэтому не будет прыжка) val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(view.windowToken, 0) - // 🔥 Показываем эмодзи сразу - без задержки! - onToggleEmojiPicker(true) - android.util.Log.d("MessageInputBar", "🎯 toggleEmojiPicker: showEmojiPicker=true, emojiPanelHeight=$emojiPanelHeight") } } @@ -2298,46 +2301,40 @@ private fun MessageInputBar( } // End of else (not blocked) - // 🔥 APPLE EMOJI PICKER - БЕЗ анимации когда клавиатура открывается + // 🔥 APPLE EMOJI PICKER - плавная анимация slide up if (!isBlocked) { - // Высота панели: 0 если клавиатура видна или эмодзи закрыты, иначе emojiPanelHeight - val targetHeight = if (isKeyboardVisible || !showEmojiPicker) 0.dp else emojiPanelHeight + // Показываем только когда клавиатура закрыта И эмодзи открыты + val showPanel = showEmojiPicker && !isKeyboardVisible - // Анимируем только когда клавиатура закрыта - val animatedHeight by animateDpAsState( - targetValue = targetHeight, - animationSpec = if (isKeyboardVisible) { - // Мгновенно когда клавиатура открывается - snap() - } else { - // Быстрая анимация (200ms) - tween(durationMillis = 200, easing = FastOutSlowInEasing) - }, - label = "EmojiPanelHeight" - ) - - // 🔥 Показываем Box только когда есть высота (НЕ занимает место когда скрыт!) - if (animatedHeight > 0.dp) { + AnimatedVisibility( + visible = showPanel, + enter = slideInVertically( + initialOffsetY = { it }, // Снизу вверх + animationSpec = tween(200, easing = FastOutSlowInEasing) + ), + exit = slideOutVertically( + targetOffsetY = { it }, // Сверху вниз + animationSpec = tween(150, easing = FastOutSlowInEasing) + ) + ) { Box( modifier = Modifier .fillMaxWidth() - .height(animatedHeight) + .height(emojiPanelHeight) .padding(bottom = 16.dp) ) { - if (showEmojiPicker) { - AppleEmojiPickerPanel( - isDarkTheme = isDarkTheme, - onEmojiSelected = { emoji -> - onValueChange(value + emoji) - }, - onClose = { - onToggleEmojiPicker(false) - }, - modifier = Modifier - .fillMaxWidth() - .height(emojiPanelHeight - 16.dp) - ) - } + AppleEmojiPickerPanel( + isDarkTheme = isDarkTheme, + onEmojiSelected = { emoji -> + onValueChange(value + emoji) + }, + onClose = { + onToggleEmojiPicker(false) + }, + modifier = Modifier + .fillMaxWidth() + .height(emojiPanelHeight - 16.dp) + ) } } } // End of if (!isBlocked) for emoji picker