fix: improve emoji picker and keyboard transition animations for smoother user experience

This commit is contained in:
k1ngsterr1
2026-02-03 00:18:49 +05:00
parent eb96d269f6
commit 5c5c5e45ee

View File

@@ -528,17 +528,52 @@ fun ImageEditorScreen(
// 🔥 КЛЮЧЕВОЕ: imePadding применяется ТОЛЬКО когда emoji НЕ показан
val shouldUseImePadding = !coordinator.isEmojiBoxVisible
// 🔥 FIX: Сырое значение - emoji показан ИЛИ box виден
val rawEmojiVisible = showEmojiPicker || coordinator.isEmojiBoxVisible
// 🔥 FIX: Debounced флаг для emoji - с задержкой при закрытии чтобы не мигал
var stableShowEmojiPicker by remember { mutableStateOf(rawEmojiVisible) }
LaunchedEffect(rawEmojiVisible) {
if (rawEmojiVisible) {
// Открытие emoji - мгновенно
stableShowEmojiPicker = true
} else {
// Закрытие emoji - с небольшой задержкой для плавности
delay(50)
stableShowEmojiPicker = false
}
}
// 🔥 FIX: Debounced флаг для стилей инпута - предотвращает мигание при закрытии emoji
// Истинный когда клавиатура ИЛИ emoji открыты
val isInputExpanded = isKeyboardVisible || stableShowEmojiPicker
// 🔥 FIX: Инициализируем с актуальным значением чтобы не было мигания при первом рендере
var isInputExpandedDebounced by remember { mutableStateOf(isInputExpanded) }
LaunchedEffect(isInputExpanded) {
if (isInputExpanded) {
// Открытие - мгновенно
isInputExpandedDebounced = true
} else {
// Закрытие - с задержкой чтобы анимация emoji завершилась
delay(300)
isInputExpandedDebounced = false
}
}
// Когда клавиатура/emoji закрыты - добавляем отступ снизу для toolbar (~100dp)
// 🔥 Анимируем отступ для плавного перехода
val bottomPaddingForCaption by animateDpAsState(
targetValue = if (!isKeyboardVisible && !coordinator.isEmojiBoxVisible) 100.dp else 0.dp,
targetValue = if (!isInputExpandedDebounced) 100.dp else 0.dp,
animationSpec = tween(200, easing = FastOutSlowInEasing),
label = "bottomPadding"
)
// 📊 Log render state
Log.d(TAG, "RENDER: showEmoji=$showEmojiPicker, isKeyboard=$isKeyboardVisible, isEmojiBoxVisible=${coordinator.isEmojiBoxVisible}, useImePadding=$shouldUseImePadding, emojiHeight=${coordinator.emojiHeight.value}dp, bottomPadding=${bottomPaddingForCaption.value}dp")
Log.d(TAG, "RENDER: showEmoji=$showEmojiPicker, stableEmoji=$stableShowEmojiPicker, isKeyboard=$isKeyboardVisible, isEmojiBoxVisible=${coordinator.isEmojiBoxVisible}, useImePadding=$shouldUseImePadding, emojiHeight=${coordinator.emojiHeight.value}dp, bottomPadding=${bottomPaddingForCaption.value}dp")
AnimatedVisibility(
visible = showCaptionInput && currentTool == EditorTool.NONE,
@@ -551,17 +586,19 @@ fun ImageEditorScreen(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = bottomPaddingForCaption)
// 🔥 imePadding ТОЛЬКО когда emoji НЕ показан
// 🔥 imePadding СНАЧАЛА - поднимает над клавиатурой
.then(if (shouldUseImePadding) Modifier.imePadding() else Modifier)
// 🔥 Затем добавляем отступ для toolbar когда клавиатура закрыта
.padding(bottom = bottomPaddingForCaption)
) {
TelegramCaptionBar(
caption = caption,
onCaptionChange = { caption = it },
isSaving = isSaving,
// 🔥 Добавляем isEmojiBoxVisible чтобы во время перехода emoji→keyboard инпут не сжимался
isKeyboardVisible = isKeyboardVisible || showEmojiPicker || coordinator.isEmojiBoxVisible,
showEmojiPicker = showEmojiPicker,
// 🔥 Используем debounced флаг чтобы стили не мигали при закрытии emoji
isKeyboardVisible = isInputExpandedDebounced,
// 🔥 FIX: Используем стабильный флаг чтобы иконка не мигала
showEmojiPicker = stableShowEmojiPicker,
onToggleEmojiPicker = { toggleEmojiPicker() },
onEditTextViewCreated = { editTextView = it },
onSend = {
@@ -979,34 +1016,40 @@ private fun TelegramCaptionBar(
onEditTextViewCreated: ((com.rosetta.messenger.ui.components.AppleEmojiEditTextView) -> Unit)? = null,
onSend: () -> Unit
) {
// Анимированный переход между стилями
// <EFBFBD> FIX: Анимируем ВСЕ параметры стиля для плавного перехода без мигания
val cornerRadius by animateDpAsState(
targetValue = if (isKeyboardVisible) 0.dp else 24.dp,
animationSpec = tween(200, easing = TelegramEasing),
animationSpec = tween(250, easing = FastOutSlowInEasing),
label = "corner"
)
val horizontalPadding by animateDpAsState(
targetValue = if (isKeyboardVisible) 0.dp else 12.dp,
animationSpec = tween(200, easing = TelegramEasing),
animationSpec = tween(250, easing = FastOutSlowInEasing),
label = "hPadding"
)
// 🔥 FIX: Анимируем прозрачность и цвет фона для плавного перехода
val backgroundAlpha by animateFloatAsState(
targetValue = if (isKeyboardVisible) 0.75f else 0.85f,
animationSpec = tween(250, easing = FastOutSlowInEasing),
label = "bgAlpha"
)
// 🔥 FIX: Плавный переход цвета фона
val backgroundColor by animateColorAsState(
targetValue = if (isKeyboardVisible) Color.Black else Color(0xFF2C2C2E),
animationSpec = tween(250, easing = FastOutSlowInEasing),
label = "bgColor"
)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding)
.then(
if (isKeyboardVisible) {
// Клавиатура/emoji открыты - полупрозрачный черный фон на всю ширину
Modifier.background(Color.Black.copy(alpha = 0.75f))
} else {
// Клавиатура закрыта - стеклянный эффект с закруглением
Modifier
.clip(RoundedCornerShape(cornerRadius))
.background(Color(0xFF2C2C2E).copy(alpha = 0.85f))
}
)
// 🔥 FIX: Единый фон с анимированными параметрами - без мигания!
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor.copy(alpha = backgroundAlpha))
.padding(horizontal = 12.dp, vertical = 10.dp)
) {
Row(
@@ -1015,11 +1058,10 @@ private fun TelegramCaptionBar(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
// Левая иконка: камера когда клавиатура закрыта, emoji/keyboard когда открыта
AnimatedContent(
// 🔥 FIX: Crossfade вместо AnimatedContent для более плавной анимации
Crossfade(
targetState = isKeyboardVisible to showEmojiPicker,
transitionSpec = {
fadeIn(tween(150)) togetherWith fadeOut(tween(150))
},
animationSpec = tween(200),
label = "left_icon"
) { (keyboardOpen, emojiOpen) ->
if (keyboardOpen) {
@@ -1036,21 +1078,30 @@ private fun TelegramCaptionBar(
)
}
} else {
// Клавиатура закрыта - камера иконка
Icon(
TablerIcons.CameraPlus,
contentDescription = "Camera",
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(26.dp)
)
// Клавиатура закрыта - камера иконка (с одинаковым размером для избежания прыжка)
Box(modifier = Modifier.size(32.dp), contentAlignment = Alignment.Center) {
Icon(
TablerIcons.CameraPlus,
contentDescription = "Camera",
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(26.dp)
)
}
}
}
// Caption text field - использует AppleEmojiTextField для правильной работы с фокусом
// 🔥 FIX: Анимируем высоту текстового поля
val textFieldMaxHeight by animateDpAsState(
targetValue = if (isKeyboardVisible) 100.dp else 24.dp,
animationSpec = tween(200, easing = FastOutSlowInEasing),
label = "textFieldHeight"
)
Box(
modifier = Modifier
.weight(1f)
.heightIn(min = 24.dp, max = if (isKeyboardVisible) 100.dp else 24.dp)
.heightIn(min = 24.dp, max = textFieldMaxHeight)
) {
AppleEmojiTextField(
value = caption,
@@ -1071,12 +1122,10 @@ private fun TelegramCaptionBar(
)
}
// Кнопка отправки
AnimatedContent(
// 🔥 FIX: Crossfade вместо AnimatedContent для кнопки отправки
Crossfade(
targetState = isKeyboardVisible,
transitionSpec = {
fadeIn(tween(150)) togetherWith fadeOut(tween(150))
},
animationSpec = tween(200),
label = "send_button"
) { keyboardOpen ->
Box(