feat: add emoji picker with smooth transition and keyboard height management

This commit is contained in:
k1ngsterr1
2026-02-02 23:33:24 +05:00
parent dc23ba9d36
commit eb96d269f6
4 changed files with 318 additions and 143 deletions

View File

@@ -43,10 +43,11 @@ fun AnimatedKeyboardTransition(
// 📊 Последнее залогированное значение alpha (для фильтрации)
val lastLoggedAlpha = remember { mutableStateOf(-1f) }
// 🔥 Клавиатура достигла ПОЛНОЙ высоты (разница не более 3dp)
val isKeyboardFullHeight = coordinator.emojiHeight > 0.dp &&
coordinator.keyboardHeight >= coordinator.emojiHeight - 3.dp
// 🔥 Клавиатура достигла высоты почти равной emoji (разница < 2dp)
// Это гарантирует плавный переход без прыжков и ререндеров
val isKeyboardNearFullHeight = coordinator.emojiHeight > 0.dp &&
coordinator.keyboardHeight >= coordinator.emojiHeight - 2.dp
// Логика перехода
if (showEmojiPicker && !wasEmojiShown) {
wasEmojiShown = true
@@ -55,10 +56,10 @@ fun AnimatedKeyboardTransition(
// Emoji закрылся после того как был открыт = переход emoji→keyboard
isTransitioningToKeyboard = true
}
// 🔥 КЛЮЧЕВОЕ: Сбрасываем флаг БЕЗ задержки когда клавиатура полностью открылась
// Проверяем прямо в composition для мгновенной реакции
if (isKeyboardFullHeight && isTransitioningToKeyboard) {
// 🔥 КЛЮЧЕВОЕ: Сбрасываем флаг когда клавиатура достигла высоты близкой к emoji
// Это предотвращает прыжок инпута при переходе
if (isKeyboardNearFullHeight && isTransitioningToKeyboard && !showEmojiPicker) {
isTransitioningToKeyboard = false
wasEmojiShown = false
}

View File

@@ -89,6 +89,10 @@ class KeyboardTransitionCoordinator {
emojiHeight = maxKeyboardHeight
}
// 🔥 КРИТИЧНО: Устанавливаем isEmojiBoxVisible СРАЗУ, ДО showEmoji()!
// Это отключает imePadding на ПЕРВОМ же рендере, предотвращая "прыжок"
isEmojiBoxVisible = true
// 🔥 ПОКАЗЫВАЕМ EMOJI СРАЗУ! Не ждем закрытия клавиатуры
showEmoji()
isEmojiVisible = true // 🔥 ВАЖНО: Устанавливаем флаг видимости emoji!

View File

@@ -6,6 +6,7 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import androidx.activity.compose.BackHandler
@@ -50,6 +51,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition
import app.rosette.android.ui.keyboard.rememberKeyboardTransitionCoordinator
import com.rosetta.messenger.ui.components.AppleEmojiTextField
import com.rosetta.messenger.ui.components.KeyboardHeightProvider
@@ -210,16 +212,20 @@ fun ImageEditorScreen(
// Отслеживание высоты клавиатуры
val imeInsets = WindowInsets.ime
var isKeyboardVisible by remember { mutableStateOf(false) }
var lastStableKeyboardHeight by remember { mutableStateOf(0.dp) }
// Update coordinator through snapshotFlow
LaunchedEffect(Unit) {
snapshotFlow { with(density) { imeInsets.getBottom(density).toDp() } }.collect { currentImeHeight ->
isKeyboardVisible = currentImeHeight > 50.dp
coordinator.updateKeyboardHeight(currentImeHeight)
if (currentImeHeight > 100.dp) {
coordinator.syncHeights()
lastStableKeyboardHeight = currentImeHeight
}
// 📊 Log IME height changes
Log.d(TAG, "IME height: ${currentImeHeight.value}dp, isKeyboardVisible: $isKeyboardVisible, emojiHeight: ${coordinator.emojiHeight.value}dp, isEmojiBoxVisible: ${coordinator.isEmojiBoxVisible}")
}
}
@@ -248,19 +254,23 @@ fun ImageEditorScreen(
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
Log.d(TAG, "toggleEmojiPicker: isEmojiVisible=${coordinator.isEmojiVisible}, isEmojiBoxVisible=${coordinator.isEmojiBoxVisible}, showEmojiPicker=$showEmojiPicker")
if (coordinator.isEmojiVisible) {
// EMOJI → KEYBOARD
Log.d(TAG, "TRANSITION: EMOJI → KEYBOARD")
coordinator.requestShowKeyboard(
showKeyboard = {
editTextView?.let { editText ->
editText.requestFocus()
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
}
},
hideEmoji = { showEmojiPicker = false }
)
} else {
// KEYBOARD → EMOJI
Log.d(TAG, "TRANSITION: KEYBOARD → EMOJI")
coordinator.requestShowEmoji(
hideKeyboard = { imm.hideSoftInputFromWindow(view.windowToken, 0) },
showEmoji = { showEmojiPicker = true }
@@ -513,42 +523,22 @@ fun ImageEditorScreen(
// ═══════════════════════════════════════════════════════════
// 📝 CAPTION INPUT + EMOJI PICKER - Telegram style
// Прячется с анимацией когда открыты инструменты (рисовалка, поворот и т.д.)
// Использует imePadding + AnimatedKeyboardTransition как в ChatDetailInput
// ═══════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════
// 🔥 TELEGRAM-STYLE FIX: Единый spacer вместо imePadding
// Это ПОЛНОСТЬЮ устраняет прыжки при переходе keyboard ↔ emoji
// ═══════════════════════════════════════════════════════════
val currentImeHeight = with(density) { WindowInsets.ime.getBottom(density).toDp() }
val isImeActuallyOpen = currentImeHeight > 50.dp
// 🔥 КЛЮЧЕВОЕ: Единая высота spacer'а - ИЛИ keyboard ИЛИ emoji
// Когда keyboard открыта: spacerHeight = imeHeight
// Когда emoji открыта: spacerHeight = emojiHeight
// Это даёт плавный переход без прыжков!
// Fallback высота emoji на случай если coordinator.emojiHeight = 0
val effectiveEmojiHeight = when {
coordinator.emojiHeight > 0.dp -> coordinator.emojiHeight
lastStableKeyboardHeight > 0.dp -> lastStableKeyboardHeight
else -> 300.dp // Минимальная fallback высота
}
val spacerHeight = when {
isImeActuallyOpen -> currentImeHeight // Keyboard открыта - её высота
showEmojiPicker || coordinator.isEmojiBoxVisible -> effectiveEmojiHeight // Emoji открыта
else -> 0.dp // Ничего не открыто
}
// Нужен ли spacer вообще
val needsSpacer = spacerHeight > 0.dp
// 🔥 Обновляем coordinator для правильной работы toolbar
coordinator.isEmojiBoxVisible = showEmojiPicker
// 🔥 КЛЮЧЕВОЕ: imePadding применяется ТОЛЬКО когда emoji НЕ показан
val shouldUseImePadding = !coordinator.isEmojiBoxVisible
// Когда клавиатура/emoji закрыты - добавляем отступ снизу для toolbar (~100dp)
val bottomPaddingForCaption = if (!needsSpacer) 100.dp else 0.dp
// 🔥 Анимируем отступ для плавного перехода
val bottomPaddingForCaption by animateDpAsState(
targetValue = if (!isKeyboardVisible && !coordinator.isEmojiBoxVisible) 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")
AnimatedVisibility(
visible = showCaptionInput && currentTool == EditorTool.NONE,
@@ -562,13 +552,15 @@ fun ImageEditorScreen(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = bottomPaddingForCaption)
// 🔥 БЕЗ imePadding! Всё контролируется через spacer ниже
// 🔥 imePadding ТОЛЬКО когда emoji НЕ показан
.then(if (shouldUseImePadding) Modifier.imePadding() else Modifier)
) {
TelegramCaptionBar(
caption = caption,
onCaptionChange = { caption = it },
isSaving = isSaving,
isKeyboardVisible = isImeActuallyOpen || showEmojiPicker,
// 🔥 Добавляем isEmojiBoxVisible чтобы во время перехода emoji→keyboard инпут не сжимался
isKeyboardVisible = isKeyboardVisible || showEmojiPicker || coordinator.isEmojiBoxVisible,
showEmojiPicker = showEmojiPicker,
onToggleEmojiPicker = { toggleEmojiPicker() },
onEditTextViewCreated = { editTextView = it },
@@ -590,30 +582,20 @@ fun ImageEditorScreen(
)
// ═══════════════════════════════════════════════════════════
// 🔥 UNIFIED SPACER: Один Box для keyboard И emoji
// Высота = imeHeight когда keyboard, = emojiHeight когда emoji
// 🔥 EMOJI PICKER - используем AnimatedKeyboardTransition
// Точно как в ChatDetailInput!
// ═══════════════════════════════════════════════════════════
if (needsSpacer) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(spacerHeight)
) {
// Emoji picker рендерится ВНУТРИ spacer'а с fade анимацией
androidx.compose.animation.AnimatedVisibility(
visible = showEmojiPicker,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
) {
OptimizedEmojiPicker(
isVisible = true,
isDarkTheme = isDarkTheme,
onEmojiSelected = { emoji -> caption = caption + emoji },
onClose = { toggleEmojiPicker() },
modifier = Modifier.fillMaxSize()
)
}
}
AnimatedKeyboardTransition(
coordinator = coordinator,
showEmojiPicker = showEmojiPicker
) {
OptimizedEmojiPicker(
isVisible = true,
isDarkTheme = isDarkTheme,
onEmojiSelected = { emoji -> caption = caption + emoji },
onClose = { toggleEmojiPicker() },
modifier = Modifier.fillMaxWidth()
)
}
}
}

View File

@@ -3,11 +3,13 @@ package com.rosetta.messenger.ui.chats.components
import android.Manifest
import android.content.ContentUris
import android.content.Context
import android.util.Log
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.*
@@ -25,9 +27,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
@@ -36,6 +40,7 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
@@ -59,7 +64,13 @@ import coil.request.ImageRequest
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.components.AppleEmojiTextField
import com.rosetta.messenger.ui.components.KeyboardHeightProvider
import com.rosetta.messenger.ui.components.OptimizedEmojiPicker
import app.rosette.android.ui.keyboard.rememberKeyboardTransitionCoordinator
import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
@@ -1383,6 +1394,11 @@ private fun formatDuration(durationMs: Long): String {
/**
* Экран предпросмотра фото с caption и кнопкой отправки (как в Telegram)
* С плавной анимацией перехода между клавиатурой и emoji picker
*
* Использует тот же подход что ChatDetailInput:
* - imePadding() применяется ТОЛЬКО когда emoji НЕ показан
* - AnimatedKeyboardTransition управляет emoji picker
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -1395,98 +1411,270 @@ fun PhotoPreviewWithCaptionScreen(
isDarkTheme: Boolean
) {
val backgroundColor = if (isDarkTheme) Color.Black else Color.White
val textColor = if (isDarkTheme) Color.White else Color.Black
val context = LocalContext.current
val view = LocalView.current
val focusManager = LocalFocusManager.current
val density = LocalDensity.current
// ═══════════════════════════════════════════════════════════════
// 😀 EMOJI PICKER STATE
// ═══════════════════════════════════════════════════════════════
var showEmojiPicker by remember { mutableStateOf(false) }
val coordinator = rememberKeyboardTransitionCoordinator()
var editTextView by remember { mutableStateOf<com.rosetta.messenger.ui.components.AppleEmojiEditTextView?>(null) }
var lastToggleTime by remember { mutableLongStateOf(0L) }
val toggleCooldownMs = 500L
// Отслеживание высоты клавиатуры
val imeInsets = WindowInsets.ime
var isKeyboardVisible by remember { mutableStateOf(false) }
var lastStableKeyboardHeight by remember { mutableStateOf(0.dp) }
// Update coordinator through snapshotFlow
LaunchedEffect(Unit) {
snapshotFlow { with(density) { imeInsets.getBottom(density).toDp() } }.collect { currentImeHeight ->
isKeyboardVisible = currentImeHeight > 50.dp
coordinator.updateKeyboardHeight(currentImeHeight)
if (currentImeHeight > 100.dp) {
coordinator.syncHeights()
lastStableKeyboardHeight = currentImeHeight
}
Log.d("PhotoPreview", "IME height: ${currentImeHeight.value}dp, isKeyboardVisible: $isKeyboardVisible, emojiHeight: ${coordinator.emojiHeight.value}dp, isEmojiBoxVisible: ${coordinator.isEmojiBoxVisible}")
}
}
// Load saved keyboard height
LaunchedEffect(Unit) {
KeyboardHeightProvider.getSavedKeyboardHeight(context)
}
// Save keyboard height when stable
LaunchedEffect(isKeyboardVisible, showEmojiPicker) {
if (isKeyboardVisible && !showEmojiPicker) {
delay(350)
if (isKeyboardVisible && !showEmojiPicker && lastStableKeyboardHeight > 300.dp) {
val heightPx = with(density) { lastStableKeyboardHeight.toPx().toInt() }
KeyboardHeightProvider.saveKeyboardHeight(context, heightPx)
}
}
}
// Toggle emoji picker function
fun toggleEmojiPicker() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastToggleTime < toggleCooldownMs) {
Log.d("PhotoPreview", "Toggle blocked by cooldown")
return
}
lastToggleTime = currentTime
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (coordinator.isEmojiVisible) {
// EMOJI → KEYBOARD
Log.d("PhotoPreview", "TOGGLE: Emoji → Keyboard")
coordinator.requestShowKeyboard(
showKeyboard = {
Log.d("PhotoPreview", "Showing keyboard...")
editTextView?.let { editText ->
editText.requestFocus()
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
}
},
hideEmoji = {
Log.d("PhotoPreview", "Hiding emoji picker")
showEmojiPicker = false
}
)
} else {
// KEYBOARD → EMOJI
Log.d("PhotoPreview", "TOGGLE: Keyboard → Emoji")
coordinator.requestShowEmoji(
hideKeyboard = {
Log.d("PhotoPreview", "Hiding keyboard...")
imm.hideSoftInputFromWindow(view.windowToken, 0)
},
showEmoji = {
Log.d("PhotoPreview", "Showing emoji picker")
showEmojiPicker = true
}
)
}
}
// 🔥 КЛЮЧЕВОЕ: imePadding применяется ТОЛЬКО когда emoji box НЕ виден
val shouldUseImePadding = !coordinator.isEmojiBoxVisible
val shouldAddNavBarPadding = !isKeyboardVisible && !coordinator.isEmojiBoxVisible
// Логируем состояние при каждой рекомпозиции
Log.d("PhotoPreview", "RENDER: showEmoji=$showEmojiPicker, isKeyboard=$isKeyboardVisible, isEmojiBoxVisible=${coordinator.isEmojiBoxVisible}, useImePadding=$shouldUseImePadding, emojiHeight=${coordinator.emojiHeight.value}dp")
Surface(
color = backgroundColor,
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
) {
// Top bar with close button
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = textColor
)
}
}
// Image preview
Box(modifier = Modifier.fillMaxSize()) {
// ═══════════════════════════════════════════════════════════
// 📸 FULLSCREEN PHOTO - не реагирует на клавиатуру
// ═══════════════════════════════════════════════════════════
AsyncImage(
model = ImageRequest.Builder(context)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit
)
// ═══════════════════════════════════════════════════════════
// 🎛️ TOP BAR - Transparent overlay
// ═══════════════════════════════════════════════════════════
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
.align(Alignment.TopCenter)
.background(
Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.5f),
Color.Transparent
)
)
)
.statusBarsPadding()
.padding(horizontal = 4.dp, vertical = 8.dp)
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit
)
}
// Caption input
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = caption,
onValueChange = onCaptionChange,
modifier = Modifier.weight(1f),
placeholder = {
Text(
"Add a caption...",
color = textColor.copy(alpha = 0.5f)
)
IconButton(
onClick = {
showEmojiPicker = false
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
focusManager.clearFocus()
onDismiss()
},
maxLines = 3,
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedTextColor = textColor,
unfocusedTextColor = textColor,
cursorColor = PrimaryBlue,
focusedBorderColor = PrimaryBlue,
unfocusedBorderColor = textColor.copy(alpha = 0.3f)
),
shape = RoundedCornerShape(20.dp)
)
// Send button
FloatingActionButton(
onClick = onSend,
containerColor = PrimaryBlue,
modifier = Modifier.size(56.dp)
modifier = Modifier.align(Alignment.CenterStart)
) {
Icon(
imageVector = TablerIcons.Send,
contentDescription = "Send",
TablerIcons.X,
contentDescription = "Close",
tint = Color.White,
modifier = Modifier.size(24.dp)
modifier = Modifier.size(28.dp)
)
}
}
// ═══════════════════════════════════════════════════════════
// 📝 CAPTION INPUT + EMOJI PICKER
// Как в ChatDetailInput: imePadding + AnimatedKeyboardTransition
// ═══════════════════════════════════════════════════════════
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
// 🔥 imePadding ТОЛЬКО когда emoji НЕ показан
.then(if (shouldUseImePadding) Modifier.imePadding() else Modifier)
) {
// Caption bar
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Black.copy(alpha = 0.75f))
.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = if (isKeyboardVisible || coordinator.isEmojiBoxVisible) 10.dp else 16.dp
)
.then(if (shouldAddNavBarPadding) Modifier.navigationBarsPadding() else Modifier)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
// Emoji/Keyboard toggle button
IconButton(
onClick = { toggleEmojiPicker() },
modifier = Modifier.size(32.dp)
) {
Crossfade(
targetState = showEmojiPicker,
animationSpec = tween(150),
label = "emojiIcon"
) { isEmoji ->
Icon(
if (isEmoji) TablerIcons.Keyboard else TablerIcons.MoodSmile,
contentDescription = if (isEmoji) "Keyboard" else "Emoji",
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(26.dp)
)
}
}
// Caption text field
Box(
modifier = Modifier
.weight(1f)
.heightIn(min = 24.dp, max = 100.dp)
) {
AppleEmojiTextField(
value = caption,
onValueChange = onCaptionChange,
textColor = Color.White,
textSize = 16f,
hint = "Add a caption...",
hintColor = Color.White.copy(alpha = 0.5f),
modifier = Modifier.fillMaxWidth(),
requestFocus = false,
onViewCreated = { v -> editTextView = v },
onFocusChanged = { hasFocus ->
if (hasFocus && showEmojiPicker) {
toggleEmojiPicker()
}
}
)
}
// Send button
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(PrimaryBlue)
.clickable { onSend() },
contentAlignment = Alignment.Center
) {
Icon(
TablerIcons.Send,
contentDescription = "Send",
tint = Color.White,
modifier = Modifier
.size(22.dp)
.offset(x = 1.dp)
)
}
}
}
// ═══════════════════════════════════════════════════════════
// 🔥 EMOJI PICKER - используем AnimatedKeyboardTransition
// Точно как в ChatDetailInput!
// ═══════════════════════════════════════════════════════════
AnimatedKeyboardTransition(
coordinator = coordinator,
showEmojiPicker = showEmojiPicker
) {
OptimizedEmojiPicker(
isVisible = true,
isDarkTheme = isDarkTheme,
onEmojiSelected = { emoji -> onCaptionChange(caption + emoji) },
onClose = { toggleEmojiPicker() },
modifier = Modifier.fillMaxWidth()
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}