feat: Implement keyboard height provider and optimize emoji picker animations
This commit is contained in:
@@ -2011,17 +2011,39 @@ private fun MessageInputBar(
|
|||||||
val imeHeight = with(density) { imeInsets.getBottom(density).toDp() }
|
val imeHeight = with(density) { imeInsets.getBottom(density).toDp() }
|
||||||
val isKeyboardVisible = imeHeight > 50.dp // 🔥 Согласованный порог с ChatDetailScreen
|
val isKeyboardVisible = imeHeight > 50.dp // 🔥 Согласованный порог с ChatDetailScreen
|
||||||
|
|
||||||
// 🔥 Запоминаем высоту клавиатуры когда она открыта
|
// 🔥 Флаг "клавиатура в процессе анимации"
|
||||||
// Дефолт 320.dp - хорошая высота для большинства устройств
|
var isKeyboardAnimating by remember { mutableStateOf(false) }
|
||||||
var savedKeyboardHeight by remember { mutableStateOf(320.dp) }
|
|
||||||
|
// 🔥 Логирование изменений высоты клавиатуры
|
||||||
LaunchedEffect(imeHeight) {
|
LaunchedEffect(imeHeight) {
|
||||||
if (imeHeight > 100.dp) {
|
android.util.Log.d("KeyboardHeight", "📊 IME height: $imeHeight (visible=$isKeyboardVisible, showEmojiPicker=$showEmojiPicker, animating=$isKeyboardAnimating)")
|
||||||
savedKeyboardHeight = imeHeight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Высота панели эмодзи = сохранённая высота клавиатуры (минимум 280.dp)
|
// 🔥 Запоминаем высоту клавиатуры когда она открыта (Telegram-style with SharedPreferences)
|
||||||
val emojiPanelHeight = maxOf(savedKeyboardHeight, 280.dp)
|
LaunchedEffect(Unit) {
|
||||||
|
// Загружаем сохранённую высоту при старте
|
||||||
|
val savedHeightPx = com.rosetta.messenger.ui.components.KeyboardHeightProvider.getSavedKeyboardHeight(context)
|
||||||
|
android.util.Log.d("KeyboardHeight", "📱 MessageInputBar initialized, loaded height: ${savedHeightPx}px")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔥 Сохраняем высоту ТОЛЬКО когда клавиатура стабильна
|
||||||
|
// Используем отдельный LaunchedEffect который НЕ реагирует на каждое изменение
|
||||||
|
LaunchedEffect(isKeyboardVisible, showEmojiPicker) {
|
||||||
|
// Если клавиатура стала видимой и emoji закрыт
|
||||||
|
if (isKeyboardVisible && !showEmojiPicker && !isKeyboardAnimating) {
|
||||||
|
// Ждем стабилизации
|
||||||
|
isKeyboardAnimating = true
|
||||||
|
kotlinx.coroutines.delay(300) // Анимация клавиатуры ~250ms
|
||||||
|
isKeyboardAnimating = false
|
||||||
|
|
||||||
|
// Сохраняем только если всё еще видна и emoji закрыт
|
||||||
|
if (isKeyboardVisible && !showEmojiPicker && imeHeight > 300.dp) {
|
||||||
|
val heightPx = with(density) { imeHeight.toPx().toInt() }
|
||||||
|
com.rosetta.messenger.ui.components.KeyboardHeightProvider.saveKeyboardHeight(context, heightPx)
|
||||||
|
android.util.Log.d("KeyboardHeight", "✅ Stable keyboard height saved: ${imeHeight} (${heightPx}px)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Состояние отправки - можно отправить если есть текст ИЛИ есть reply
|
// Состояние отправки - можно отправить если есть текст ИЛИ есть reply
|
||||||
val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply }
|
val canSend = remember(value, hasReply) { value.isNotBlank() || hasReply }
|
||||||
@@ -2049,14 +2071,19 @@ private fun MessageInputBar(
|
|||||||
fun toggleEmojiPicker() {
|
fun toggleEmojiPicker() {
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker called, showEmojiPicker=$showEmojiPicker")
|
android.util.Log.d("EmojiPicker", "=".repeat(60))
|
||||||
|
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker START")
|
||||||
|
android.util.Log.d("EmojiPicker", " State: showEmojiPicker=$showEmojiPicker, isKeyboardVisible=$isKeyboardVisible")
|
||||||
|
android.util.Log.d("EmojiPicker", " IME height: $imeHeight, editTextView=${if (editTextView != null) "SET" else "NULL"}")
|
||||||
|
|
||||||
if (showEmojiPicker) {
|
if (showEmojiPicker) {
|
||||||
// ========== ЗАКРЫВАЕМ EMOJI → ОТКРЫВАЕМ КЛАВИАТУРУ ==========
|
// ========== ЗАКРЫВАЕМ EMOJI → ОТКРЫВАЕМ КЛАВИАТУРУ ==========
|
||||||
android.util.Log.d("EmojiPicker", "📱 Closing emoji, opening keyboard")
|
android.util.Log.d("EmojiPicker", "📱 Action: CLOSING emoji → OPENING keyboard")
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
// Шаг 1: Закрываем emoji панель
|
// Шаг 1: Закрываем emoji панель
|
||||||
onToggleEmojiPicker(false)
|
onToggleEmojiPicker(false)
|
||||||
|
android.util.Log.d("EmojiPicker", " [1] Emoji panel closed")
|
||||||
|
|
||||||
// Шаг 2: Немедленно фокусируем и открываем клавиатуру
|
// Шаг 2: Немедленно фокусируем и открываем клавиатуру
|
||||||
editTextView?.let { editText ->
|
editTextView?.let { editText ->
|
||||||
@@ -2064,44 +2091,51 @@ private fun MessageInputBar(
|
|||||||
|
|
||||||
// Метод 1: Немедленный вызов
|
// Метод 1: Немедленный вызов
|
||||||
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
|
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
|
||||||
android.util.Log.d("EmojiPicker", "✅ Called showSoftInput FORCED (immediate)")
|
android.util.Log.d("EmojiPicker", " [2] Method 1: showSoftInput(FORCED) called")
|
||||||
|
|
||||||
// Метод 2: Через post (следующий frame)
|
// Метод 2: Через post (следующий frame)
|
||||||
view.post {
|
view.post {
|
||||||
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
android.util.Log.d("EmojiPicker", "✅ Called showSoftInput IMPLICIT (post)")
|
android.util.Log.d("EmojiPicker", " [3] Method 2: showSoftInput(IMPLICIT) called (${elapsed}ms)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Метод 3: Через postDelayed (100ms)
|
// Метод 3: Через postDelayed (100ms)
|
||||||
view.postDelayed({
|
view.postDelayed({
|
||||||
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
|
||||||
android.util.Log.d("EmojiPicker", "✅ Called toggleSoftInput (100ms)")
|
android.util.Log.d("EmojiPicker", " [4] Method 3: toggleSoftInput called (${elapsed}ms)")
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
// Метод 4: Финальная попытка через 200ms
|
// Метод 4: Финальная попытка через 200ms
|
||||||
view.postDelayed({
|
view.postDelayed({
|
||||||
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
if (!isKeyboardVisible) {
|
if (!isKeyboardVisible) {
|
||||||
android.util.Log.d("EmojiPicker", "⚠️ Keyboard still not visible, forcing again")
|
android.util.Log.w("EmojiPicker", " [5] ⚠️ Keyboard still not visible after ${elapsed}ms, forcing again")
|
||||||
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
|
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
|
||||||
} else {
|
} else {
|
||||||
android.util.Log.d("EmojiPicker", "✅ Keyboard is visible!")
|
android.util.Log.d("EmojiPicker", " [5] ✅ Keyboard opened successfully in ${elapsed}ms!")
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
} ?: android.util.Log.e("EmojiPicker", "❌ editTextView is null!")
|
} ?: android.util.Log.e("EmojiPicker", " ❌ ERROR: editTextView is null!")
|
||||||
} else {
|
} else {
|
||||||
// ========== ОТКРЫВАЕМ EMOJI → ЗАКРЫВАЕМ КЛАВИАТУРУ ==========
|
// ========== ОТКРЫВАЕМ EMOJI → ЗАКРЫВАЕМ КЛАВИАТУРУ ==========
|
||||||
android.util.Log.d("EmojiPicker", "😊 Opening emoji, closing keyboard")
|
android.util.Log.d("EmojiPicker", "😊 Action: OPENING emoji → CLOSING keyboard")
|
||||||
|
|
||||||
// Шаг 1: Скрываем клавиатуру
|
// Шаг 1: Скрываем клавиатуру
|
||||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
|
android.util.Log.d("EmojiPicker", " [1] Keyboard hide requested")
|
||||||
|
|
||||||
// Шаг 2: Небольшая задержка для плавности
|
// Шаг 2: Небольшая задержка для плавности
|
||||||
view.postDelayed({
|
view.postDelayed({
|
||||||
// Шаг 3: Открываем emoji панель
|
// Шаг 3: Открываем emoji панель
|
||||||
onToggleEmojiPicker(true)
|
onToggleEmojiPicker(true)
|
||||||
android.util.Log.d("EmojiPicker", "✅ Emoji panel opened")
|
android.util.Log.d("EmojiPicker", " [2] ✅ Emoji panel opened (50ms delay)")
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android.util.Log.d("EmojiPicker", "🔥 toggleEmojiPicker END")
|
||||||
|
android.util.Log.d("EmojiPicker", "=".repeat(60))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция отправки - НЕ закрывает клавиатуру (UX правило #6)
|
// Функция отправки - НЕ закрывает клавиатуру (UX правило #6)
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.rosetta.messenger.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🎯 KEYBOARD HEIGHT PROVIDER
|
||||||
|
*
|
||||||
|
* Вдохновлено Telegram's EmojiView.java:
|
||||||
|
* - Сохраняет высоту клавиатуры в SharedPreferences
|
||||||
|
* - Использует сохранённую высоту для emoji picker
|
||||||
|
* - Обновляет при изменении высоты клавиатуры
|
||||||
|
*
|
||||||
|
* Telegram код:
|
||||||
|
* ```java
|
||||||
|
* keyboardHeight = MessagesController.getGlobalEmojiSettings()
|
||||||
|
* .getInt("kbd_height", AndroidUtilities.dp(200));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
object KeyboardHeightProvider {
|
||||||
|
private const val PREFS_NAME = "emoji_keyboard_prefs"
|
||||||
|
private const val KEY_KEYBOARD_HEIGHT = "kbd_height"
|
||||||
|
private const val DEFAULT_HEIGHT_DP = 280 // Telegram uses 200, we use 280
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить сохранённую высоту клавиатуры
|
||||||
|
*/
|
||||||
|
fun getSavedKeyboardHeight(context: Context): Int {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val defaultPx = dpToPx(context, DEFAULT_HEIGHT_DP)
|
||||||
|
val savedPx = prefs.getInt(KEY_KEYBOARD_HEIGHT, defaultPx)
|
||||||
|
val isDefault = savedPx == defaultPx
|
||||||
|
|
||||||
|
android.util.Log.d("KeyboardHeight", "📖 getSavedKeyboardHeight: ${savedPx}px (${pxToDp(context, savedPx)}dp) ${if (isDefault) "[DEFAULT]" else "[SAVED]"}")
|
||||||
|
return savedPx
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить высоту клавиатуры
|
||||||
|
*/
|
||||||
|
fun saveKeyboardHeight(context: Context, heightPx: Int) {
|
||||||
|
if (heightPx > 0) {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val oldHeight = prefs.getInt(KEY_KEYBOARD_HEIGHT, -1)
|
||||||
|
val changed = oldHeight != heightPx
|
||||||
|
|
||||||
|
prefs.edit().putInt(KEY_KEYBOARD_HEIGHT, heightPx).apply()
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
android.util.Log.d("KeyboardHeight", "💾 SAVED keyboard height: ${heightPx}px (${pxToDp(context, heightPx)}dp) [WAS: ${oldHeight}px]")
|
||||||
|
} else {
|
||||||
|
android.util.Log.v("KeyboardHeight", "💾 Same keyboard height: ${heightPx}px (no change)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
android.util.Log.w("KeyboardHeight", "⚠️ Attempted to save invalid height: ${heightPx}px")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить длительность анимации клавиатуры
|
||||||
|
* Telegram использует: AdjustPanLayoutHelper.keyboardDuration (250ms обычно)
|
||||||
|
*/
|
||||||
|
fun getKeyboardAnimationDuration(): Long = 250L
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
private fun dpToPx(context: Context, dp: Int): Int {
|
||||||
|
val density = context.resources.displayMetrics.density
|
||||||
|
return (dp * density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pxToDp(context: Context, px: Int): Int {
|
||||||
|
val density = context.resources.displayMetrics.density
|
||||||
|
return (px / density).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable для получения высоты клавиатуры из SharedPreferences
|
||||||
|
* НЕ использует remember - всегда берет актуальное значение из SharedPreferences
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberSavedKeyboardHeight(): Dp {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// 🔥 НЕ используем remember - каждый раз читаем актуальное значение
|
||||||
|
val heightPx = KeyboardHeightProvider.getSavedKeyboardHeight(context)
|
||||||
|
val density = context.resources.displayMetrics.density
|
||||||
|
val heightDp = (heightPx / density).dp
|
||||||
|
|
||||||
|
android.util.Log.d("KeyboardHeight", "🎯 rememberSavedKeyboardHeight: ${heightDp} (${heightPx}px, density=${density})")
|
||||||
|
|
||||||
|
return heightDp
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
@@ -44,9 +45,10 @@ import kotlinx.coroutines.launch
|
|||||||
* 2. LazyGrid с оптимизированными настройками (beyondBoundsLayout)
|
* 2. LazyGrid с оптимизированными настройками (beyondBoundsLayout)
|
||||||
* 3. Hardware layer для анимаций
|
* 3. Hardware layer для анимаций
|
||||||
* 4. Минимум recomposition (derivedStateOf, remember keys)
|
* 4. Минимум recomposition (derivedStateOf, remember keys)
|
||||||
* 5. Smooth slide + fade transitions
|
* 5. Smooth slide + fade transitions (Telegram-style)
|
||||||
* 6. Coil оптимизация (hardware acceleration, size limits)
|
* 6. Coil оптимизация (hardware acceleration, size limits)
|
||||||
* 7. Нет лишних indications/ripples
|
* 7. SharedPreferences для сохранения высоты клавиатуры (как в Telegram)
|
||||||
|
* 8. keyboardDuration для синхронизации с системной клавиатурой
|
||||||
*
|
*
|
||||||
* @param isVisible Видимость панели
|
* @param isVisible Видимость панели
|
||||||
* @param isDarkTheme Темная/светлая тема
|
* @param isDarkTheme Темная/светлая тема
|
||||||
@@ -63,36 +65,47 @@ fun OptimizedEmojiPicker(
|
|||||||
onClose: () -> Unit = {},
|
onClose: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
// 🎭 Быстрая и плавная анимация (как в Telegram)
|
// 🔥 Используем сохранённую высоту клавиатуры (как в Telegram)
|
||||||
|
val savedKeyboardHeight = rememberSavedKeyboardHeight()
|
||||||
|
|
||||||
|
// 🔥 Telegram's keyboardDuration для синхронизации анимации
|
||||||
|
val animationDuration = KeyboardHeightProvider.getKeyboardAnimationDuration().toInt()
|
||||||
|
|
||||||
|
// 🔥 Логирование изменений видимости
|
||||||
|
LaunchedEffect(isVisible) {
|
||||||
|
android.util.Log.d("EmojiPicker", "🎭 OptimizedEmojiPicker visibility changed: $isVisible (height=${savedKeyboardHeight}, animDuration=${animationDuration}ms)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🎭 Telegram-style анимация: используем сохранённую длительность
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isVisible,
|
visible = isVisible,
|
||||||
enter = slideInVertically(
|
enter = slideInVertically(
|
||||||
initialOffsetY = { it },
|
initialOffsetY = { it },
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 180, // 🔥 Быстрее!
|
durationMillis = animationDuration, // 🔥 Telegram's 250ms
|
||||||
easing = FastOutSlowInEasing
|
easing = FastOutSlowInEasing
|
||||||
)
|
)
|
||||||
) + fadeIn(
|
) + fadeIn(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 120,
|
durationMillis = animationDuration / 2,
|
||||||
easing = LinearEasing
|
easing = LinearEasing
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
exit = slideOutVertically(
|
exit = slideOutVertically(
|
||||||
targetOffsetY = { it },
|
targetOffsetY = { it },
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 150, // 🔥 Быстрое закрытие
|
durationMillis = (animationDuration * 0.8).toInt(), // 🔥 Быстрое закрытие (200ms)
|
||||||
easing = FastOutLinearInEasing
|
easing = FastOutLinearInEasing
|
||||||
)
|
)
|
||||||
) + fadeOut(
|
) + fadeOut(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 100,
|
durationMillis = (animationDuration * 0.6).toInt(),
|
||||||
easing = LinearEasing
|
easing = LinearEasing
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
// 🎨 Hardware layer для анимаций (GPU ускорение)
|
// 🎨 Hardware layer для анимаций (GPU ускорение - как в Telegram)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.graphicsLayer {
|
modifier = Modifier.graphicsLayer {
|
||||||
// Используем hardware layer только во время анимации
|
// Используем hardware layer только во время анимации
|
||||||
@@ -103,7 +116,8 @@ fun OptimizedEmojiPicker(
|
|||||||
) {
|
) {
|
||||||
EmojiPickerContent(
|
EmojiPickerContent(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
onEmojiSelected = onEmojiSelected
|
onEmojiSelected = onEmojiSelected,
|
||||||
|
keyboardHeight = savedKeyboardHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +129,8 @@ fun OptimizedEmojiPicker(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun EmojiPickerContent(
|
private fun EmojiPickerContent(
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onEmojiSelected: (String) -> Unit
|
onEmojiSelected: (String) -> Unit,
|
||||||
|
keyboardHeight: Dp
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
|
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
|
||||||
@@ -126,13 +141,19 @@ private fun EmojiPickerContent(
|
|||||||
var shouldRenderContent by remember { mutableStateOf(false) }
|
var shouldRenderContent by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
android.util.Log.d("EmojiPicker", "🚀 EmojiPickerContent started, keyboardHeight=$keyboardHeight")
|
||||||
|
|
||||||
// Ждём 1 кадр чтобы анимация началась плавно
|
// Ждём 1 кадр чтобы анимация началась плавно
|
||||||
kotlinx.coroutines.delay(16) // ~1 frame at 60fps
|
kotlinx.coroutines.delay(16) // ~1 frame at 60fps
|
||||||
shouldRenderContent = true
|
shouldRenderContent = true
|
||||||
|
android.util.Log.d("EmojiPicker", "✅ Content rendering enabled after 16ms delay")
|
||||||
|
|
||||||
// Загружаем эмодзи если еще не загружены
|
// Загружаем эмодзи если еще не загружены
|
||||||
if (!OptimizedEmojiCache.isLoaded) {
|
if (!OptimizedEmojiCache.isLoaded) {
|
||||||
|
android.util.Log.d("EmojiPicker", "📦 Starting emoji preload...")
|
||||||
OptimizedEmojiCache.preload(context)
|
OptimizedEmojiCache.preload(context)
|
||||||
|
} else {
|
||||||
|
android.util.Log.d("EmojiPicker", "✅ Emojis already loaded")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +170,11 @@ private fun EmojiPickerContent(
|
|||||||
|
|
||||||
// 🚀 При смене категории плавно скроллим наверх
|
// 🚀 При смене категории плавно скроллим наверх
|
||||||
LaunchedEffect(selectedCategory) {
|
LaunchedEffect(selectedCategory) {
|
||||||
|
android.util.Log.d("EmojiPicker", "📂 Category changed: ${selectedCategory.key} (${displayedEmojis.size} emojis)")
|
||||||
if (displayedEmojis.isNotEmpty()) {
|
if (displayedEmojis.isNotEmpty()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
gridState.animateScrollToItem(0)
|
gridState.animateScrollToItem(0)
|
||||||
|
android.util.Log.v("EmojiPicker", "⬆️ Scrolled to top")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +187,7 @@ private fun EmojiPickerContent(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(350.dp) // Фиксированная высота как у клавиатуры
|
.height(height = keyboardHeight) // 🔥 Используем сохранённую высоту (как в Telegram)
|
||||||
.background(panelBackground)
|
.background(panelBackground)
|
||||||
) {
|
) {
|
||||||
// 🔥 Показываем пустую панель пока не готово
|
// 🔥 Показываем пустую панель пока не готово
|
||||||
|
|||||||
Reference in New Issue
Block a user