fix: improve emoji picker and keyboard transition animations for smoother user experience
This commit is contained in:
@@ -529,16 +529,51 @@ 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
|
||||
// 🔥 FIX: Единый фон с анимированными параметрами - без мигания!
|
||||
.clip(RoundedCornerShape(cornerRadius))
|
||||
.background(Color(0xFF2C2C2E).copy(alpha = 0.85f))
|
||||
}
|
||||
)
|
||||
.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,7 +1078,8 @@ private fun TelegramCaptionBar(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Клавиатура закрыта - камера иконка
|
||||
// Клавиатура закрыта - камера иконка (с одинаковым размером для избежания прыжка)
|
||||
Box(modifier = Modifier.size(32.dp), contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
TablerIcons.CameraPlus,
|
||||
contentDescription = "Camera",
|
||||
@@ -1045,12 +1088,20 @@ private fun TelegramCaptionBar(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
|
||||
Reference in New Issue
Block a user