fix: improve emoji picker and keyboard transition animations for smoother user experience
This commit is contained in:
@@ -528,17 +528,52 @@ 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(
|
||||||
|
|||||||
Reference in New Issue
Block a user