From c4043cd2478ba0a614f9ecf310708737990aa05d Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Thu, 15 Jan 2026 02:29:06 +0500 Subject: [PATCH] feat: Refactor emoji picker behavior and improve animation performance --- .../messenger/ui/chats/ChatDetailScreen.kt | 90 ++++++++++++++----- .../ui/components/OptimizedEmojiPicker.kt | 83 ++++++++++------- 2 files changed, 121 insertions(+), 52 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 309be44..1db7846 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 @@ -2026,15 +2026,8 @@ 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) - } - } + // 🔥 УДАЛЕНО: автоматическое закрытие emoji при открытии клавиатуры + // Теперь это контролируется только через toggleEmojiPicker() // 🔥 Закрываем клавиатуру когда пользователь заблокирован LaunchedEffect(isBlocked) { @@ -2052,23 +2045,62 @@ private fun MessageInputBar( focusManager.clearFocus(force = true) } - // 🔥 Функция переключения emoji picker - TELEGRAM STYLE (без прыжков) + // 🔥 Функция переключения emoji picker - МАКСИМАЛЬНО АГРЕССИВНОЕ ОТКРЫТИЕ КЛАВИАТУРЫ fun toggleEmojiPicker() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + + android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker called, showEmojiPicker=$showEmojiPicker") + if (showEmojiPicker) { - // Закрываем emoji picker и открываем клавиатуру + // ========== ЗАКРЫВАЕМ EMOJI → ОТКРЫВАЕМ КЛАВИАТУРУ ========== + android.util.Log.d("EmojiPicker", "📱 Closing emoji, opening keyboard") + + // Шаг 1: Закрываем emoji панель onToggleEmojiPicker(false) - // Открываем клавиатуру + + // Шаг 2: Немедленно фокусируем и открываем клавиатуру editTextView?.let { editText -> editText.requestFocus() - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) - } + + // Метод 1: Немедленный вызов + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED) + android.util.Log.d("EmojiPicker", "✅ Called showSoftInput FORCED (immediate)") + + // Метод 2: Через post (следующий frame) + view.post { + imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + android.util.Log.d("EmojiPicker", "✅ Called showSoftInput IMPLICIT (post)") + } + + // Метод 3: Через postDelayed (100ms) + view.postDelayed({ + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + android.util.Log.d("EmojiPicker", "✅ Called toggleSoftInput (100ms)") + }, 100) + + // Метод 4: Финальная попытка через 200ms + view.postDelayed({ + if (!isKeyboardVisible) { + android.util.Log.d("EmojiPicker", "⚠️ Keyboard still not visible, forcing again") + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED) + } else { + android.util.Log.d("EmojiPicker", "✅ Keyboard is visible!") + } + }, 200) + } ?: android.util.Log.e("EmojiPicker", "❌ editTextView is null!") } else { - // 🔥 Сначала показываем эмодзи панель - onToggleEmojiPicker(true) - // Потом скрываем клавиатуру (панель уже видна, поэтому не будет прыжка) - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + // ========== ОТКРЫВАЕМ EMOJI → ЗАКРЫВАЕМ КЛАВИАТУРУ ========== + android.util.Log.d("EmojiPicker", "😊 Opening emoji, closing keyboard") + + // Шаг 1: Скрываем клавиатуру imm.hideSoftInputFromWindow(view.windowToken, 0) + + // Шаг 2: Небольшая задержка для плавности + view.postDelayed({ + // Шаг 3: Открываем emoji панель + onToggleEmojiPicker(true) + android.util.Log.d("EmojiPicker", "✅ Emoji panel opened") + }, 50) } } @@ -2247,9 +2279,20 @@ private fun MessageInputBar( interactionSource = remember { MutableInteractionSource() }, indication = null ) { - // При клике на инпут - закрываем эмодзи панель, клавиатура откроется автоматически + // При клике на инпут - закрываем эмодзи панель и открываем клавиатуру if (showEmojiPicker) { onToggleEmojiPicker(false) + // Открываем клавиатуру после небольшой задержки + view.postDelayed({ + editTextView?.let { editText -> + editText.requestFocus() + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED) + } + }, 100) + } else { + // Просто фокусируем для открытия клавиатуры + editTextView?.requestFocus() } } .padding(horizontal = 12.dp, vertical = 8.dp), @@ -2263,7 +2306,12 @@ private fun MessageInputBar( hint = "Type message...", hintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93), modifier = Modifier.fillMaxWidth(), - requestFocus = hasReply + requestFocus = hasReply, + onViewCreated = { view -> + // 🔥 Сохраняем ссылку на EditText для программного открытия клавиатуры + editTextView = view + android.util.Log.d("EmojiPicker", "✅ editTextView set: $view") + } ) } diff --git a/app/src/main/java/com/rosetta/messenger/ui/components/OptimizedEmojiPicker.kt b/app/src/main/java/com/rosetta/messenger/ui/components/OptimizedEmojiPicker.kt index 18ccf87..b932591 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/components/OptimizedEmojiPicker.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/components/OptimizedEmojiPicker.kt @@ -63,30 +63,30 @@ fun OptimizedEmojiPicker( onClose: () -> Unit = {}, modifier: Modifier = Modifier ) { - // 🎭 Smooth animation для открытия/закрытия + // 🎭 Быстрая и плавная анимация (как в Telegram) AnimatedVisibility( visible = isVisible, enter = slideInVertically( initialOffsetY = { it }, animationSpec = tween( - durationMillis = 250, + durationMillis = 180, // 🔥 Быстрее! easing = FastOutSlowInEasing ) ) + fadeIn( animationSpec = tween( - durationMillis = 200, + durationMillis = 120, easing = LinearEasing ) ), exit = slideOutVertically( targetOffsetY = { it }, animationSpec = tween( - durationMillis = 200, + durationMillis = 150, // 🔥 Быстрое закрытие easing = FastOutLinearInEasing ) ) + fadeOut( animationSpec = tween( - durationMillis = 150, + durationMillis = 100, easing = LinearEasing ) ), @@ -122,8 +122,15 @@ private fun EmojiPickerContent( val gridState = rememberLazyGridState() val scope = rememberCoroutineScope() - // 🚀 Загружаем эмодзи если еще не загружены + // 🚀 Отложенный рендеринг - даём анимации начаться без фриза + var shouldRenderContent by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + // Ждём 1 кадр чтобы анимация началась плавно + kotlinx.coroutines.delay(16) // ~1 frame at 60fps + shouldRenderContent = true + + // Загружаем эмодзи если еще не загружены if (!OptimizedEmojiCache.isLoaded) { OptimizedEmojiCache.preload(context) } @@ -160,31 +167,45 @@ private fun EmojiPickerContent( .height(350.dp) // Фиксированная высота как у клавиатуры .background(panelBackground) ) { - // ============ КАТЕГОРИИ ============ - CategoryBar( - categories = EMOJI_CATEGORIES, - selectedCategory = selectedCategory, - onCategorySelected = { selectedCategory = it }, - isDarkTheme = isDarkTheme, - backgroundColor = categoryBarBackground - ) - - // ============ РАЗДЕЛИТЕЛЬ ============ - Divider( - modifier = Modifier - .fillMaxWidth() - .height(0.5.dp), - color = dividerColor - ) - - // ============ СЕТКА ЭМОДЗИ ============ - EmojiGrid( - isLoaded = OptimizedEmojiCache.isLoaded, - emojis = displayedEmojis, - gridState = gridState, - onEmojiSelected = onEmojiSelected, - isDarkTheme = isDarkTheme - ) + // 🔥 Показываем пустую панель пока не готово + if (!shouldRenderContent) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = PrimaryBlue, + strokeWidth = 2.dp + ) + } + } else { + // ============ КАТЕГОРИИ ============ + CategoryBar( + categories = EMOJI_CATEGORIES, + selectedCategory = selectedCategory, + onCategorySelected = { selectedCategory = it }, + isDarkTheme = isDarkTheme, + backgroundColor = categoryBarBackground + ) + + // ============ РАЗДЕЛИТЕЛЬ ============ + Divider( + modifier = Modifier + .fillMaxWidth() + .height(0.5.dp), + color = dividerColor + ) + + // ============ СЕТКА ЭМОДЗИ ============ + EmojiGrid( + isLoaded = OptimizedEmojiCache.isLoaded, + emojis = displayedEmojis, + gridState = gridState, + onEmojiSelected = onEmojiSelected, + isDarkTheme = isDarkTheme + ) + } } }