# 🎯 План плавной смены клавиатур (Telegram-style)

## 📊 Анализ проблемы

### Текущая ситуация:

- ✅ Высота клавиатур одинаковая (364.95dp)
- ❌ UI дергается при переключении клавиатур
- ❌ Нет синхронизации анимаций
- ❌ Emoji панель появляется/исчезает резко

### Как работает Telegram:

**Ключевые компоненты:**

1. **AdjustPanLayoutHelper** - координирует анимации клавиатуры
2. **EditTextEmoji.showPopup()** - управляет переключением
3. **ValueAnimator** - анимирует translationY
4. **250ms duration + keyboardInterpolator** (FastOutSlowIn)

**Важные паттерны Telegram:**

```java
// 1. Синхронная анимация - emoji панель двигается вместе с keyboard
animator.setDuration(AdjustPanLayoutHelper.keyboardDuration); // 250ms
animator.setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator);

// 2. TranslationY вместо изменения height
emojiView.setTranslationY(v); // плавно двигаем панель

// 3. Одновременное скрытие keyboard и показ emoji
if (!keyboardVisible && !emojiWasVisible) {
    // анимируем появление emoji снизу
    ValueAnimator animator = ValueAnimator.ofFloat(emojiPadding, 0);
}
```

## 🎬 Архитектура решения для Compose

### Phase 1: Механизм синхронизации анимаций

#### 1.1 KeyboardTransitionCoordinator

**Цель:** Координировать переходы между клавиатурами

```kotlin
@Composable
fun rememberKeyboardTransitionCoordinator(): KeyboardTransitionCoordinator {
    return remember {
        KeyboardTransitionCoordinator()
    }
}

class KeyboardTransitionCoordinator {
    // Состояния перехода
    enum class TransitionState {
        IDLE,                    // Ничего не происходит
        KEYBOARD_TO_EMOJI,       // Keyboard → Emoji
        EMOJI_TO_KEYBOARD,       // Emoji → Keyboard
        KEYBOARD_OPENING,        // Только keyboard открывается
        EMOJI_OPENING,           // Только emoji открывается
        KEYBOARD_CLOSING,        // Только keyboard закрывается
        EMOJI_CLOSING            // Только emoji закрывается
    }

    var currentState by mutableStateOf(TransitionState.IDLE)
    var transitionProgress by mutableFloatStateOf(0f)

    // Высоты для расчетов
    var keyboardHeight by mutableStateOf(0.dp)
    var emojiHeight by mutableStateOf(0.dp)

    // Флаги
    var isKeyboardVisible by mutableStateOf(false)
    var isEmojiVisible by mutableStateOf(false)

    // Запуск перехода
    fun startTransition(
        from: TransitionState,
        to: TransitionState,
        onComplete: () -> Unit
    ) {
        currentState = to
        // Логика анимации
    }
}
```

#### 1.2 Compose Animatable для плавности

```kotlin
val emojiOffsetY = remember { Animatable(0f) }
val keyboardOffsetY = remember { Animatable(0f) }

// Telegram-style timing
val transitionSpec = tween<Float>(
    durationMillis = 250,
    easing = FastOutSlowInEasing // keyboardInterpolator
)
```

### Phase 2: Управление состояниями

#### 2.1 State Machine для переходов

```kotlin
sealed class KeyboardState {
    object Closed : KeyboardState()
    object SystemKeyboardOpen : KeyboardState()
    object EmojiKeyboardOpen : KeyboardState()
    data class Transitioning(
        val from: KeyboardState,
        val to: KeyboardState,
        val progress: Float
    ) : KeyboardState()
}

class KeyboardStateManager {
    var currentState by mutableStateOf<KeyboardState>(KeyboardState.Closed)

    fun transition(target: KeyboardState) {
        when (currentState to target) {
            KeyboardState.SystemKeyboardOpen to KeyboardState.EmojiKeyboardOpen -> {
                // Keyboard → Emoji
                startKeyboardToEmojiTransition()
            }
            KeyboardState.EmojiKeyboardOpen to KeyboardState.SystemKeyboardOpen -> {
                // Emoji → Keyboard
                startEmojiToKeyboardTransition()
            }
            // ... другие переходы
        }
    }
}
```

### Phase 3: Анимация переходов

#### 3.1 Keyboard → Emoji (самый сложный)

**Проблема:** Системная клавиатура закрывается асинхронно

**Решение Telegram:**

1. **Не ждать закрытия клавиатуры**
2. **Сразу показать emoji панель на месте клавиатуры**
3. **Анимировать emoji снизу вверх, пока keyboard уходит**

```kotlin
@Composable
fun KeyboardToEmojiTransition(
    coordinator: KeyboardTransitionCoordinator,
    onComplete: () -> Unit
) {
    val offsetY = remember { Animatable(coordinator.keyboardHeight.value) }

    LaunchedEffect(Unit) {
        // Шаг 1: Скрыть клавиатуру (асинхронно)
        coordinator.hideSystemKeyboard()

        // Шаг 2: Сразу анимировать emoji снизу (250ms)
        offsetY.animateTo(
            targetValue = 0f,
            animationSpec = tween(
                durationMillis = 250,
                easing = FastOutSlowInEasing
            )
        )

        onComplete()
    }

    // Emoji панель с offset
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(coordinator.emojiHeight)
            .offset(y = offsetY.value.dp)
    ) {
        OptimizedEmojiPicker(...)
    }
}
```

#### 3.2 Emoji → Keyboard

```kotlin
@Composable
fun EmojiToKeyboardTransition(
    coordinator: KeyboardTransitionCoordinator,
    onComplete: () -> Unit
) {
    val emojiOffsetY = remember { Animatable(0f) }

    LaunchedEffect(Unit) {
        // Шаг 1: Показать клавиатуру (асинхронно)
        coordinator.showSystemKeyboard()

        // Шаг 2: Одновременно анимировать emoji вниз (250ms)
        emojiOffsetY.animateTo(
            targetValue = coordinator.emojiHeight.value,
            animationSpec = tween(
                durationMillis = 250,
                easing = FastOutLinearInEasing // быстрее уходит
            )
        )

        onComplete()
    }

    // Emoji панель уходит вниз
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(coordinator.emojiHeight)
            .offset(y = emojiOffsetY.value.dp)
            .alpha(1f - (emojiOffsetY.value / coordinator.emojiHeight.value))
    ) {
        OptimizedEmojiPicker(...)
    }
}
```

### Phase 4: Интеграция с IME

#### 4.1 WindowInsets синхронизация

```kotlin
@Composable
fun MessageInputBar() {
    val coordinator = rememberKeyboardTransitionCoordinator()
    val ime = WindowInsets.ime
    val imeHeight = ime.getBottom(LocalDensity.current).toDp()

    // Отслеживаем изменения IME
    LaunchedEffect(imeHeight) {
        coordinator.keyboardHeight = imeHeight

        // Если IME закрылась во время показа emoji
        if (imeHeight == 0.dp && coordinator.isEmojiVisible) {
            // Продолжить показ emoji без дерганья
            coordinator.currentState = TransitionState.IDLE
        }
    }
}
```

#### 4.2 Spacer для резервации места

**Ключевая идея Telegram:** Emoji панель резервирует место клавиатуры

```kotlin
@Composable
fun KeyboardSpacer(
    coordinator: KeyboardTransitionCoordinator
) {
    val height by animateDpAsState(
        targetValue = when {
            coordinator.isKeyboardVisible -> coordinator.keyboardHeight
            coordinator.isEmojiVisible -> coordinator.emojiHeight
            else -> 0.dp
        },
        animationSpec = tween(
            durationMillis = 250,
            easing = FastOutSlowInEasing
        )
    )

    Spacer(modifier = Modifier.height(height))
}
```

## 🔧 Детальная имплементация

### Шаг 1: KeyboardTransitionCoordinator.kt

```kotlin
class KeyboardTransitionCoordinator(
    private val context: Context
) {
    companion object {
        const val TRANSITION_DURATION = 250L
        val TRANSITION_EASING = FastOutSlowInEasing
    }

    // Состояние
    var keyboardHeight by mutableStateOf(0.dp)
    var emojiHeight by mutableStateOf(0.dp)
    var isKeyboardVisible by mutableStateOf(false)
    var isEmojiVisible by mutableStateOf(false)
    var isTransitioning by mutableStateOf(false)

    // Для отладки
    private val tag = "KeyboardTransition"

    fun requestShowEmoji(
        hideKeyboard: () -> Unit,
        showEmoji: () -> Unit
    ) {
        Log.d(tag, "🔄 Keyboard → Emoji transition started")
        isTransitioning = true

        // Telegram паттерн: сначала скрыть клавиатуру
        hideKeyboard()

        // Через небольшую задержку показать emoji
        // (даем системе начать закрытие клавиатуры)
        Handler(Looper.getMainLooper()).postDelayed({
            showEmoji()
            isEmojiVisible = true
            isKeyboardVisible = false
            isTransitioning = false
            Log.d(tag, "✅ Emoji visible, keyboard hidden")
        }, 50L)
    }

    fun requestShowKeyboard(
        showKeyboard: () -> Unit,
        hideEmoji: () -> Unit
    ) {
        Log.d(tag, "🔄 Emoji → Keyboard transition started")
        isTransitioning = true

        // Сначала показать клавиатуру
        showKeyboard()

        // Emoji скрыть после начала анимации
        Handler(Looper.getMainLooper()).postDelayed({
            hideEmoji()
            isEmojiVisible = false
            isKeyboardVisible = true
            isTransitioning = false
            Log.d(tag, "✅ Keyboard visible, emoji hidden")
        }, 50L)
    }
}
```

### Шаг 2: Обновить MessageInputBar

```kotlin
@Composable
fun MessageInputBar(...) {
    val coordinator = rememberKeyboardTransitionCoordinator()
    val focusRequester = remember { FocusRequester() }
    val keyboardController = LocalSoftwareKeyboardController.current

    // Отслеживание IME
    val ime = WindowInsets.ime
    val imeHeight = with(LocalDensity.current) {
        ime.getBottom(this).toDp()
    }

    // Обновляем высоту клавиатуры
    LaunchedEffect(imeHeight) {
        if (imeHeight > 100.dp) {
            coordinator.keyboardHeight = imeHeight
            coordinator.isKeyboardVisible = true
        } else if (imeHeight == 0.dp && !coordinator.isEmojiVisible) {
            coordinator.isKeyboardVisible = false
        }
        Log.d("KeyboardHeight", "IME height: $imeHeight")
    }

    Column {
        // Input field
        Row {
            TextField(...)

            // Emoji button
            IconButton(
                onClick = {
                    if (coordinator.isEmojiVisible) {
                        // Переключить на клавиатуру
                        coordinator.requestShowKeyboard(
                            showKeyboard = {
                                focusRequester.requestFocus()
                                keyboardController?.show()
                            },
                            hideEmoji = {
                                showEmojiPicker = false
                            }
                        )
                    } else {
                        // Переключить на emoji
                        coordinator.requestShowEmoji(
                            hideKeyboard = {
                                keyboardController?.hide()
                            },
                            showEmoji = {
                                showEmojiPicker = true
                                coordinator.emojiHeight = coordinator.keyboardHeight
                            }
                        )
                    }
                }
            ) {
                Icon(
                    imageVector = if (coordinator.isEmojiVisible) {
                        Icons.Default.Keyboard
                    } else {
                        Icons.Default.EmojiEmotions
                    }
                )
            }
        }

        // Emoji picker с анимацией
        AnimatedKeyboardTransition(
            coordinator = coordinator,
            showEmojiPicker = showEmojiPicker
        )
    }
}
```

### Шаг 3: AnimatedKeyboardTransition.kt

```kotlin
@Composable
fun AnimatedKeyboardTransition(
    coordinator: KeyboardTransitionCoordinator,
    showEmojiPicker: Boolean
) {
    val offsetY = remember { Animatable(0f) }
    val alpha = remember { Animatable(0f) }

    LaunchedEffect(showEmojiPicker) {
        if (showEmojiPicker) {
            // Показать emoji
            launch {
                offsetY.snapTo(coordinator.emojiHeight.value)
                offsetY.animateTo(
                    targetValue = 0f,
                    animationSpec = tween(
                        durationMillis = 250,
                        easing = FastOutSlowInEasing
                    )
                )
            }
            launch {
                alpha.snapTo(0f)
                alpha.animateTo(
                    targetValue = 1f,
                    animationSpec = tween(durationMillis = 200)
                )
            }
        } else {
            // Скрыть emoji
            launch {
                offsetY.animateTo(
                    targetValue = coordinator.emojiHeight.value,
                    animationSpec = tween(
                        durationMillis = 200,
                        easing = FastOutLinearInEasing
                    )
                )
            }
            launch {
                alpha.animateTo(
                    targetValue = 0f,
                    animationSpec = tween(durationMillis = 150)
                )
            }
        }
    }

    if (showEmojiPicker || offsetY.value > 0f) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(coordinator.emojiHeight)
                .offset(y = offsetY.value.dp)
                .alpha(alpha.value)
        ) {
            OptimizedEmojiPicker(
                keyboardHeight = coordinator.emojiHeight,
                ...
            )
        }
    }
}
```

## 📈 Этапы внедрения

### Этап 1: Базовая инфраструктура (1-2 часа)

- [ ] Создать `KeyboardTransitionCoordinator.kt`
- [ ] Добавить state management для переходов
- [ ] Настроить логирование для отладки

### Этап 2: Анимации (2-3 часа)

- [ ] Создать `AnimatedKeyboardTransition.kt`
- [ ] Реализовать Keyboard → Emoji переход
- [ ] Реализовать Emoji → Keyboard переход
- [ ] Добавить fade анимацию для плавности

### Этап 3: Интеграция (1-2 часа)

- [ ] Обновить `MessageInputBar` с coordinator
- [ ] Интегрировать с `OptimizedEmojiPicker`
- [ ] Добавить `KeyboardSpacer` для резервации места

### Этап 4: Полировка (1-2 часа)

- [ ] Синхронизация с IME events
- [ ] Обработка edge cases (поворот экрана, многозадачность)
- [ ] Убрать дерганья при быстрых переключениях
- [ ] Тестирование на разных устройствах

### Этап 5: Оптимизация (1 час)

- [ ] Убрать избыточные recomposition
- [ ] Добавить `remember` где нужно
- [ ] Проверить производительность
- [ ] Удалить debug логи (опционально)

## 🎨 Ключевые паттерны Telegram

### 1. **Немедленное резервирование места**

```kotlin
// Telegram сразу резервирует место для emoji панели
emojiPadding = currentHeight
sizeNotifierLayout.requestLayout()
```

### 2. **TranslationY вместо show/hide**

```kotlin
// Анимация позиции, а не visibility
emojiView.setTranslationY(v)
```

### 3. **Синхронные анимации**

```kotlin
// Все анимации используют одинаковый timing
animator.setDuration(AdjustPanLayoutHelper.keyboardDuration); // 250ms
animator.setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator);
```

### 4. **Плавный alpha для emoji**

```kotlin
// Fade in/out для естественности
emojiViewAlpha = 1f - v / (float) emojiPadding;
emojiView.setAlpha(emojiViewAlpha);
```

## 🐛 Возможные проблемы и решения

### Проблема 1: IME закрывается слишком медленно

**Решение:** Не ждать закрытия, показать emoji сразу

```kotlin
// Telegram паттерн
if (!keyboardVisible && !emojiWasVisible) {
    // Анимировать emoji снизу, пока keyboard закрывается
}
```

### Проблема 2: UI дергается при переключении

**Решение:** Резервировать место спейсером

```kotlin
Spacer(modifier = Modifier.height(max(keyboardHeight, emojiHeight)))
```

### Проблема 3: Разные высоты на landscape/portrait

**Решение:** Сохранять отдельно для каждой ориентации

```kotlin
val keyboardHeight = if (isLandscape) keyboardHeightLand else keyboardHeightPort
```

### Проблема 4: Быстрые переключения

**Решение:** Отменять предыдущие анимации

```kotlin
LaunchedEffect(showEmojiPicker) {
    offsetY.stop() // Остановить текущую анимацию
    // Начать новую
}
```

## 📊 Ожидаемый результат

### До:

- ❌ UI дергается
- ❌ Резкое появление/исчезновение
- ❌ Несинхронные анимации
- ❌ Пустое место при переключении

### После:

- ✅ Плавный переход за 250ms
- ✅ Синхронизированные анимации
- ✅ Telegram-style UX
- ✅ Нет дерганий UI
- ✅ Резервированное место

## 🚀 Готовы начинать?

План готов! Начнем с создания `KeyboardTransitionCoordinator.kt` - это основа всей системы. После этого добавим анимации и интегрируем в `MessageInputBar`.

Telegram решает эту задачу через:

1. **ValueAnimator** с 250ms и FastOutSlowIn
2. **TranslationY** для плавного движения
3. **Немедленное резервирование места**
4. **Синхронизация всех анимаций**

Готов приступить к реализации! 🎯
