feat: enhance keyboard height management and emoji picker synchronization in GroupSetupScreen

This commit is contained in:
2026-03-04 14:34:17 +05:00
parent d1aca8439a
commit 62857da793
2 changed files with 43 additions and 23 deletions

View File

@@ -54,7 +54,7 @@ class KeyboardTransitionCoordinator {
var keyboardHeight by mutableStateOf(0.dp) var keyboardHeight by mutableStateOf(0.dp)
var emojiHeight by mutableStateOf(0.dp) var emojiHeight by mutableStateOf(0.dp)
// 🔥 Сохраняем максимальную высоту клавиатуры для правильного восстановления emoji // Максимальная высота клавиатуры (защищает от промежуточных значений при анимации закрытия)
private var maxKeyboardHeight by mutableStateOf(0.dp) private var maxKeyboardHeight by mutableStateOf(0.dp)
// ============ Флаги видимости ============ // ============ Флаги видимости ============
@@ -84,8 +84,10 @@ class KeyboardTransitionCoordinator {
currentState = TransitionState.KEYBOARD_TO_EMOJI currentState = TransitionState.KEYBOARD_TO_EMOJI
isTransitioning = true isTransitioning = true
// 🔥 Гарантируем что emojiHeight = maxKeyboardHeight (не меняется при закрытии клавиатуры) // Устанавливаем emojiHeight = текущая высота клавиатуры (клавиатура ещё открыта)
if (maxKeyboardHeight > 0.dp) { if (keyboardHeight > 0.dp) {
emojiHeight = keyboardHeight
} else if (maxKeyboardHeight > 0.dp) {
emojiHeight = maxKeyboardHeight emojiHeight = maxKeyboardHeight
} }
@@ -234,7 +236,7 @@ class KeyboardTransitionCoordinator {
if (height > 100.dp && height != keyboardHeight) { if (height > 100.dp && height != keyboardHeight) {
keyboardHeight = height keyboardHeight = height
// 🔥 Сохраняем максимальную высоту // Обновляем maxKeyboardHeight только вверх (защита от промежуточных значений анимации)
if (height > maxKeyboardHeight) { if (height > maxKeyboardHeight) {
maxKeyboardHeight = height maxKeyboardHeight = height
} }
@@ -244,14 +246,14 @@ class KeyboardTransitionCoordinator {
emojiHeight = height emojiHeight = height
} }
} else if (height == 0.dp && keyboardHeight != 0.dp) { } else if (height == 0.dp && keyboardHeight != 0.dp) {
// 🔥 Клавиатура закрывается - восстанавливаем emojiHeight до МАКСИМАЛЬНОЙ высоты // Клавиатура закрывается.
// Если emoji уже показан (keyboard→emoji переход), НЕ трогаем emojiHeight —
// Восстанавливаем emojiHeight до максимальной высоты // requestShowEmoji() уже установил правильное значение = текущая высота клавиатуры.
if (maxKeyboardHeight > 0.dp) { // Восстанавливаем из maxKeyboardHeight только если emoji НЕ виден (обычное закрытие).
if (!isEmojiVisible && !isEmojiBoxVisible && maxKeyboardHeight > 0.dp) {
emojiHeight = maxKeyboardHeight emojiHeight = maxKeyboardHeight
} }
// Обнуляем keyboardHeight
keyboardHeight = 0.dp keyboardHeight = 0.dp
} }
@@ -272,7 +274,8 @@ class KeyboardTransitionCoordinator {
* emojiHeight должна оставаться фиксированной! * emojiHeight должна оставаться фиксированной!
*/ */
fun syncHeights() { fun syncHeights() {
// 🔥 Синхронизируем ТОЛЬКО если клавиатура ОТКРЫТА и высота больше текущей emoji // Синхронизируем только вверх — при закрытии клавиатуры промежуточные значения
// не должны уменьшать emojiHeight. Точная высота ставится в requestShowEmoji().
if (keyboardHeight > 100.dp && keyboardHeight > emojiHeight) { if (keyboardHeight > 100.dp && keyboardHeight > emojiHeight) {
emojiHeight = keyboardHeight emojiHeight = keyboardHeight
} }

View File

@@ -7,6 +7,8 @@ import android.view.inputmethod.InputMethodManager
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -243,15 +245,28 @@ fun GroupSetupScreen(
} }
var lastStableKeyboardHeight by remember { mutableStateOf(0.dp) } var lastStableKeyboardHeight by remember { mutableStateOf(0.dp) }
var isKeyboardVisible by remember { mutableStateOf(false) }
LaunchedEffect(imeBottomPx) { LaunchedEffect(imeBottomPx) {
val currentImeHeight = with(density) { imeBottomPx.toDp() } val currentImeHeight = with(density) { imeBottomPx.toDp() }
coordinator.updateKeyboardHeight(currentImeHeight) coordinator.updateKeyboardHeight(currentImeHeight)
isKeyboardVisible = currentImeHeight > 50.dp
if (currentImeHeight > 100.dp) { if (currentImeHeight > 100.dp) {
coordinator.syncHeights() coordinator.syncHeights()
lastStableKeyboardHeight = currentImeHeight lastStableKeyboardHeight = currentImeHeight
} }
} }
// Save keyboard height for emoji picker sync
LaunchedEffect(isKeyboardVisible, showEmojiKeyboard) {
if (isKeyboardVisible && !showEmojiKeyboard) {
kotlinx.coroutines.delay(350)
if (isKeyboardVisible && !showEmojiKeyboard && lastStableKeyboardHeight > 300.dp) {
val heightPx = with(density) { lastStableKeyboardHeight.toPx().toInt() }
KeyboardHeightProvider.saveKeyboardHeight(context, heightPx)
}
}
}
fun toggleEmojiPicker() { fun toggleEmojiPicker() {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now - lastToggleTime < toggleCooldownMs || step != GroupSetupStep.DETAILS || isLoading) return if (now - lastToggleTime < toggleCooldownMs || step != GroupSetupStep.DETAILS || isLoading) return
@@ -725,22 +740,24 @@ fun GroupSetupScreen(
containerColor = if (actionEnabled) accentColor else accentColor.copy(alpha = 0.42f), containerColor = if (actionEnabled) accentColor else accentColor.copy(alpha = 0.42f),
contentColor = Color.White, contentColor = Color.White,
shape = CircleShape, shape = CircleShape,
modifier = modifier = run {
Modifier var lastStableFabBottom by remember { mutableStateOf(18.dp) }
.align(Alignment.BottomEnd) val rawBottom = if (imeBottomDp > 0.dp) {
.padding(
end = 16.dp,
// Consistent: always 14dp above whichever keyboard is showing.
// This Box ignores paddingValues, so we measure from raw screen bottom.
bottom = if (imeBottomDp > 0.dp) {
imeBottomDp + 14.dp imeBottomDp + 14.dp
} else if (coordinator.isEmojiBoxVisible && coordinator.emojiHeight > 0.dp) { } else if (coordinator.isEmojiBoxVisible && coordinator.emojiHeight > 0.dp) {
coordinator.emojiHeight + 14.dp coordinator.emojiHeight + 14.dp
} else { } else {
18.dp 18.dp
} }
) // During keyboard switch, keep FAB at last stable position
if (!coordinator.isTransitioning) {
lastStableFabBottom = rawBottom
}
Modifier
.align(Alignment.BottomEnd)
.padding(end = 16.dp, bottom = lastStableFabBottom)
.size(58.dp) .size(58.dp)
}
) { ) {
if (isLoading && step == GroupSetupStep.DESCRIPTION) { if (isLoading && step == GroupSetupStep.DESCRIPTION) {
CircularProgressIndicator( CircularProgressIndicator(