feat: Refactor emoji picker behavior and improve animation performance

This commit is contained in:
k1ngsterr1
2026-01-15 02:29:06 +05:00
parent 35e21fd3f6
commit c4043cd247
2 changed files with 121 additions and 52 deletions

View File

@@ -2026,15 +2026,8 @@ private fun MessageInputBar(
// Состояние отправки - можно отправить если есть текст ИЛИ есть reply // Состояние отправки - можно отправить если есть текст ИЛИ есть reply
val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply } val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply }
// 🔥 Плавно закрываем эмодзи панель когда клавиатура ПОЛНОСТЬЮ открылась // 🔥 УДАЛЕНО: автоматическое закрытие emoji при открытии клавиатуры
// Используем LaunchedEffect с debounce чтобы избежать дёрганья // Теперь это контролируется только через toggleEmojiPicker()
LaunchedEffect(isKeyboardVisible) {
if (isKeyboardVisible && showEmojiPicker) {
// Ждём пока клавиатура полностью поднимется (200ms)
kotlinx.coroutines.delay(200)
onToggleEmojiPicker(false)
}
}
// 🔥 Закрываем клавиатуру когда пользователь заблокирован // 🔥 Закрываем клавиатуру когда пользователь заблокирован
LaunchedEffect(isBlocked) { LaunchedEffect(isBlocked) {
@@ -2052,23 +2045,62 @@ private fun MessageInputBar(
focusManager.clearFocus(force = true) focusManager.clearFocus(force = true)
} }
// 🔥 Функция переключения emoji picker - TELEGRAM STYLE (без прыжков) // 🔥 Функция переключения emoji picker - МАКСИМАЛЬНО АГРЕССИВНОЕ ОТКРЫТИЕ КЛАВИАТУРЫ
fun toggleEmojiPicker() { fun toggleEmojiPicker() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker called, showEmojiPicker=$showEmojiPicker")
if (showEmojiPicker) { if (showEmojiPicker) {
// Закрываем emoji picker и открываем клавиатуру // ========== ЗАКРЫВАЕМ EMOJI → ОТКРЫВАЕМ КЛАВИАТУРУ ==========
android.util.Log.d("EmojiPicker", "📱 Closing emoji, opening keyboard")
// Шаг 1: Закрываем emoji панель
onToggleEmojiPicker(false) onToggleEmojiPicker(false)
// Открываем клавиатуру
// Шаг 2: Немедленно фокусируем и открываем клавиатуру
editTextView?.let { editText -> editTextView?.let { editText ->
editText.requestFocus() 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 { } else {
// 🔥 Сначала показываем эмодзи панель // ========== ОТКРЫВАЕМ EMOJI → ЗАКРЫВАЕМ КЛАВИАТУРУ ==========
onToggleEmojiPicker(true) android.util.Log.d("EmojiPicker", "😊 Opening emoji, closing keyboard")
// Потом скрываем клавиатуру (панель уже видна, поэтому не будет прыжка)
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager // Шаг 1: Скрываем клавиатуру
imm.hideSoftInputFromWindow(view.windowToken, 0) 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() }, interactionSource = remember { MutableInteractionSource() },
indication = null indication = null
) { ) {
// При клике на инпут - закрываем эмодзи панель, клавиатура откроется автоматически // При клике на инпут - закрываем эмодзи панель и открываем клавиатуру
if (showEmojiPicker) { if (showEmojiPicker) {
onToggleEmojiPicker(false) 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), .padding(horizontal = 12.dp, vertical = 8.dp),
@@ -2263,7 +2306,12 @@ private fun MessageInputBar(
hint = "Type message...", hint = "Type message...",
hintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93), hintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
requestFocus = hasReply requestFocus = hasReply,
onViewCreated = { view ->
// 🔥 Сохраняем ссылку на EditText для программного открытия клавиатуры
editTextView = view
android.util.Log.d("EmojiPicker", "✅ editTextView set: $view")
}
) )
} }

View File

@@ -63,30 +63,30 @@ fun OptimizedEmojiPicker(
onClose: () -> Unit = {}, onClose: () -> Unit = {},
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
// 🎭 Smooth animation для открытия/закрытия // 🎭 Быстрая и плавная анимация (как в Telegram)
AnimatedVisibility( AnimatedVisibility(
visible = isVisible, visible = isVisible,
enter = slideInVertically( enter = slideInVertically(
initialOffsetY = { it }, initialOffsetY = { it },
animationSpec = tween( animationSpec = tween(
durationMillis = 250, durationMillis = 180, // 🔥 Быстрее!
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
) )
) + fadeIn( ) + fadeIn(
animationSpec = tween( animationSpec = tween(
durationMillis = 200, durationMillis = 120,
easing = LinearEasing easing = LinearEasing
) )
), ),
exit = slideOutVertically( exit = slideOutVertically(
targetOffsetY = { it }, targetOffsetY = { it },
animationSpec = tween( animationSpec = tween(
durationMillis = 200, durationMillis = 150, // 🔥 Быстрое закрытие
easing = FastOutLinearInEasing easing = FastOutLinearInEasing
) )
) + fadeOut( ) + fadeOut(
animationSpec = tween( animationSpec = tween(
durationMillis = 150, durationMillis = 100,
easing = LinearEasing easing = LinearEasing
) )
), ),
@@ -122,8 +122,15 @@ private fun EmojiPickerContent(
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// 🚀 Загружаем эмодзи если еще не загружены // 🚀 Отложенный рендеринг - даём анимации начаться без фриза
var shouldRenderContent by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// Ждём 1 кадр чтобы анимация началась плавно
kotlinx.coroutines.delay(16) // ~1 frame at 60fps
shouldRenderContent = true
// Загружаем эмодзи если еще не загружены
if (!OptimizedEmojiCache.isLoaded) { if (!OptimizedEmojiCache.isLoaded) {
OptimizedEmojiCache.preload(context) OptimizedEmojiCache.preload(context)
} }
@@ -160,31 +167,45 @@ private fun EmojiPickerContent(
.height(350.dp) // Фиксированная высота как у клавиатуры .height(350.dp) // Фиксированная высота как у клавиатуры
.background(panelBackground) .background(panelBackground)
) { ) {
// ============ КАТЕГОРИИ ============ // 🔥 Показываем пустую панель пока не готово
CategoryBar( if (!shouldRenderContent) {
categories = EMOJI_CATEGORIES, Box(
selectedCategory = selectedCategory, modifier = Modifier.fillMaxSize(),
onCategorySelected = { selectedCategory = it }, contentAlignment = Alignment.Center
isDarkTheme = isDarkTheme, ) {
backgroundColor = categoryBarBackground CircularProgressIndicator(
) modifier = Modifier.size(24.dp),
color = PrimaryBlue,
// ============ РАЗДЕЛИТЕЛЬ ============ strokeWidth = 2.dp
Divider( )
modifier = Modifier }
.fillMaxWidth() } else {
.height(0.5.dp), // ============ КАТЕГОРИИ ============
color = dividerColor CategoryBar(
) categories = EMOJI_CATEGORIES,
selectedCategory = selectedCategory,
// ============ СЕТКА ЭМОДЗИ ============ onCategorySelected = { selectedCategory = it },
EmojiGrid( isDarkTheme = isDarkTheme,
isLoaded = OptimizedEmojiCache.isLoaded, backgroundColor = categoryBarBackground
emojis = displayedEmojis, )
gridState = gridState,
onEmojiSelected = onEmojiSelected, // ============ РАЗДЕЛИТЕЛЬ ============
isDarkTheme = isDarkTheme Divider(
) modifier = Modifier
.fillMaxWidth()
.height(0.5.dp),
color = dividerColor
)
// ============ СЕТКА ЭМОДЗИ ============
EmojiGrid(
isLoaded = OptimizedEmojiCache.isLoaded,
emojis = displayedEmojis,
gridState = gridState,
onEmojiSelected = onEmojiSelected,
isDarkTheme = isDarkTheme
)
}
} }
} }