feat: Implement smooth keyboard transition plan with Telegram-style animations

- Add KeyboardTransitionCoordinator for managing transitions between keyboard and emoji panel.
- Create AnimatedKeyboardTransition for handling emoji panel animations with slide and fade effects.
- Integrate keyboard transition logic into MessageInputBar for seamless emoji picker toggling.
- Update OptimizedEmojiPicker to utilize external animation management instead of internal visibility animations.
- Ensure synchronization of keyboard and emoji heights for consistent UI behavior.
This commit is contained in:
2026-01-15 12:08:10 +05:00
parent a075f98dcb
commit 9f4e85d64a
6 changed files with 1300 additions and 155 deletions

View File

@@ -208,7 +208,8 @@ fun AppleEmojiTextField(
hint: String = "Message",
hintColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.Gray,
onViewCreated: ((AppleEmojiEditTextView) -> Unit)? = null,
requestFocus: Boolean = false
requestFocus: Boolean = false,
onFocusChanged: ((Boolean) -> Unit)? = null
) {
// Храним ссылку на view для управления фокусом
var editTextView by remember { mutableStateOf<AppleEmojiEditTextView?>(null) }
@@ -236,6 +237,14 @@ fun AppleEmojiTextField(
setBackgroundColor(android.graphics.Color.TRANSPARENT)
// Сохраняем ссылку на view
editTextView = this
// Подключаем callback для изменения фокуса
setOnFocusChangeListener { _, hasFocus ->
android.util.Log.d("AppleEmojiTextField", "═══════════════════════════════════════════════════════")
android.util.Log.d("AppleEmojiTextField", "🎯 Native EditText focus changed: hasFocus=$hasFocus")
android.util.Log.d("AppleEmojiTextField", " 📍 Calling onFocusChanged callback...")
onFocusChanged?.invoke(hasFocus)
android.util.Log.d("AppleEmojiTextField", " ✅ onFocusChanged callback completed")
}
// Уведомляем о создании view
onViewCreated?.invoke(this)
}

View File

@@ -45,15 +45,15 @@ import kotlinx.coroutines.launch
* 2. LazyGrid с оптимизированными настройками (beyondBoundsLayout)
* 3. Hardware layer для анимаций
* 4. Минимум recomposition (derivedStateOf, remember keys)
* 5. Smooth slide + fade transitions (Telegram-style)
* 6. Coil оптимизация (hardware acceleration, size limits)
* 7. SharedPreferences для сохранения высоты клавиатуры (как в Telegram)
* 8. keyboardDuration для синхронизации с системной клавиатурой
* 5. Coil оптимизация (hardware acceleration, size limits)
* 6. SharedPreferences для сохранения высоты клавиатуры (как в Telegram)
* 7. keyboardDuration для синхронизации с системной клавиатурой
* 8. Анимация управляется внешним AnimatedKeyboardTransition
*
* @param isVisible Видимость панели
* @param isVisible Видимость панели (для внутренней логики)
* @param isDarkTheme Темная/светлая тема
* @param onEmojiSelected Callback при выборе эмодзи
* @param onClose Callback при закрытии (не используется, панель просто скрывается)
* @param onClose Callback при закрытии
* @param modifier Модификатор
*/
@OptIn(ExperimentalAnimationApi::class)
@@ -68,59 +68,19 @@ fun OptimizedEmojiPicker(
// 🔥 Используем сохранённую высоту клавиатуры (как в 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)")
android.util.Log.d("EmojiPicker", "🎭 OptimizedEmojiPicker visibility: $isVisible (height=${savedKeyboardHeight})")
}
// 🎭 Telegram-style анимация: используем сохранённую длительность
AnimatedVisibility(
visible = isVisible,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = tween(
durationMillis = animationDuration, // 🔥 Telegram's 250ms
easing = FastOutSlowInEasing
)
) + fadeIn(
animationSpec = tween(
durationMillis = animationDuration / 2,
easing = LinearEasing
)
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = tween(
durationMillis = (animationDuration * 0.8).toInt(), // 🔥 Быстрое закрытие (200ms)
easing = FastOutLinearInEasing
)
) + fadeOut(
animationSpec = tween(
durationMillis = (animationDuration * 0.6).toInt(),
easing = LinearEasing
)
),
// 🔥 Рендерим контент напрямую без AnimatedVisibility
// Анимация теперь управляется AnimatedKeyboardTransition
EmojiPickerContent(
isDarkTheme = isDarkTheme,
onEmojiSelected = onEmojiSelected,
keyboardHeight = savedKeyboardHeight,
modifier = modifier
) {
// 🎨 Hardware layer для анимаций (GPU ускорение - как в Telegram)
Box(
modifier = Modifier.graphicsLayer {
// Используем hardware layer только во время анимации
if (transition.isRunning) {
this.alpha = 1f
}
}
) {
EmojiPickerContent(
isDarkTheme = isDarkTheme,
onEmojiSelected = onEmojiSelected,
keyboardHeight = savedKeyboardHeight
)
}
}
)
}
/**
@@ -130,7 +90,8 @@ fun OptimizedEmojiPicker(
private fun EmojiPickerContent(
isDarkTheme: Boolean,
onEmojiSelected: (String) -> Unit,
keyboardHeight: Dp
keyboardHeight: Dp,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
@@ -185,7 +146,7 @@ private fun EmojiPickerContent(
val dividerColor = if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f)
Column(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.height(height = keyboardHeight) // 🔥 Используем сохранённую высоту (как в Telegram)
.background(panelBackground)