feat: Refactor emoji picker behavior and improve animation performance
This commit is contained in:
@@ -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")
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user