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

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