feat: Implement Telegram-style emoji picker with dynamic height and improved user experience
This commit is contained in:
@@ -12,6 +12,8 @@ import androidx.compose.foundation.combinedClickable
|
|||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
@@ -41,6 +43,7 @@ import androidx.compose.ui.graphics.vector.path
|
|||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -1515,8 +1518,6 @@ private fun MessageInputBar(
|
|||||||
) {
|
) {
|
||||||
val hasReply = replyMessages.isNotEmpty()
|
val hasReply = replyMessages.isNotEmpty()
|
||||||
var showEmojiPicker by remember { mutableStateOf(false) }
|
var showEmojiPicker by remember { mutableStateOf(false) }
|
||||||
// Флаг для запуска закрытия клавиатуры перед открытием emoji picker
|
|
||||||
var pendingEmojiPicker by remember { mutableStateOf(false) }
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
@@ -1525,43 +1526,37 @@ private fun MessageInputBar(
|
|||||||
// Получаем context и view для гарантированного закрытия клавиатуры
|
// Получаем context и view для гарантированного закрытия клавиатуры
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
// 🔥 Отслеживаем высоту клавиатуры (Telegram-style)
|
||||||
|
val imeInsets = WindowInsets.ime
|
||||||
|
val imeHeight = with(density) { imeInsets.getBottom(density).toDp() }
|
||||||
|
val isKeyboardVisible = imeHeight > 0.dp
|
||||||
|
|
||||||
|
// Высота панели эмодзи = высота клавиатуры (или 280dp по умолчанию)
|
||||||
|
val emojiPanelHeight = if (imeHeight > 50.dp) imeHeight else 280.dp
|
||||||
|
|
||||||
// Состояние отправки
|
// Состояние отправки
|
||||||
val canSend = remember(value) { value.isNotBlank() }
|
val canSend = remember(value) { value.isNotBlank() }
|
||||||
|
|
||||||
// Easing анимации
|
|
||||||
val backEasing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1f)
|
|
||||||
|
|
||||||
// Функция для гарантированного закрытия клавиатуры через InputMethodManager
|
// Функция для гарантированного закрытия клавиатуры через InputMethodManager
|
||||||
fun hideKeyboardCompletely() {
|
fun hideKeyboardCompletely() {
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
focusManager.clearFocus(force = true)
|
focusManager.clearFocus(force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Эффект для закрытия клавиатуры и открытия emoji picker
|
|
||||||
LaunchedEffect(pendingEmojiPicker) {
|
|
||||||
if (pendingEmojiPicker) {
|
|
||||||
// Гарантированно закрываем клавиатуру через InputMethodManager
|
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
|
||||||
focusManager.clearFocus(force = true)
|
|
||||||
// Ждём пока клавиатура закроется
|
|
||||||
delay(200)
|
|
||||||
// Теперь открываем emoji picker
|
|
||||||
showEmojiPicker = true
|
|
||||||
pendingEmojiPicker = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция переключения emoji picker
|
// 🔥 Функция переключения emoji picker - TELEGRAM STYLE (без прыжков)
|
||||||
fun toggleEmojiPicker() {
|
fun toggleEmojiPicker() {
|
||||||
if (showEmojiPicker) {
|
if (showEmojiPicker) {
|
||||||
// Закрываем emoji picker
|
// Закрываем emoji picker и открываем клавиатуру
|
||||||
showEmojiPicker = false
|
showEmojiPicker = false
|
||||||
|
// Клавиатура откроется автоматически т.к. фокус остался
|
||||||
} else {
|
} else {
|
||||||
// Запускаем процесс: сначала закрыть клавиатуру, потом открыть picker
|
// Закрываем клавиатуру но оставляем фокус
|
||||||
pendingEmojiPicker = true
|
keyboardController?.hide()
|
||||||
|
// Сразу показываем emoji panel (высота та же - нет прыжка)
|
||||||
|
showEmojiPicker = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1765,18 +1760,95 @@ private fun MessageInputBar(
|
|||||||
}
|
}
|
||||||
} // End of else (not blocked)
|
} // End of else (not blocked)
|
||||||
|
|
||||||
// Apple Emoji Picker - только показываем если не заблокирован
|
// 🔥 TELEGRAM-STYLE EMOJI PICKER - фиксированная высота как у клавиатуры
|
||||||
if (!isBlocked) {
|
if (!isBlocked) {
|
||||||
|
// Показываем когда showEmojiPicker = true ИЛИ когда клавиатура закрывается
|
||||||
|
// Это предотвращает прыжок инпута
|
||||||
|
val shouldShowEmojiSpace = showEmojiPicker || (isKeyboardVisible && showEmojiPicker)
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = showEmojiPicker,
|
visible = showEmojiPicker,
|
||||||
enter = expandVertically(expandFrom = Alignment.Bottom) + fadeIn(),
|
enter = fadeIn(tween(150)),
|
||||||
exit = shrinkVertically(shrinkTowards = Alignment.Bottom) + fadeOut()
|
exit = fadeOut(tween(100))
|
||||||
) {
|
) {
|
||||||
AppleEmojiPickerPanel(
|
// Telegram-style simple emoji grid - высота = высота клавиатуры
|
||||||
isDarkTheme = isDarkTheme,
|
Column(
|
||||||
onEmojiSelected = { emoji -> onValueChange(value + emoji) },
|
modifier = Modifier
|
||||||
onClose = { showEmojiPicker = false }
|
.fillMaxWidth()
|
||||||
)
|
.height(emojiPanelHeight) // 🔥 Динамическая высота
|
||||||
|
.background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7))
|
||||||
|
) {
|
||||||
|
// Топ бар с кнопкой закрытия (как в Telegram)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showEmojiPicker = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.KeyboardArrowDown,
|
||||||
|
contentDescription = "Close",
|
||||||
|
tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Простая сетка эмодзи (самые популярные)
|
||||||
|
val emojis = remember {
|
||||||
|
listOf(
|
||||||
|
"😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂",
|
||||||
|
"🙂", "🙃", "😉", "😊", "😇", "🥰", "😍", "🤩",
|
||||||
|
"😘", "😗", "😚", "😙", "😋", "😛", "😜", "🤪",
|
||||||
|
"😝", "🤑", "🤗", "🤭", "🤫", "🤔", "🤐", "🤨",
|
||||||
|
"😐", "😑", "😶", "😏", "😒", "🙄", "😬", "🤥",
|
||||||
|
"😌", "😔", "😪", "🤤", "😴", "😷", "🤒", "🤕",
|
||||||
|
"🤢", "🤮", "🤧", "🥵", "🥶", "😶", "😵", "🤯",
|
||||||
|
"🤠", "🥳", "😎", "🤓", "🧐", "😕", "😟", "🙁",
|
||||||
|
"☹️", "😮", "😯", "😲", "😳", "🥺", "😦", "😧",
|
||||||
|
"😨", "😰", "😥", "😢", "😭", "😱", "😖", "😣",
|
||||||
|
"😞", "😓", "😩", "😫", "🥱", "😤", "😡", "😠",
|
||||||
|
"🤬", "😈", "👿", "💀", "☠️", "💩", "🤡", "👹",
|
||||||
|
"👺", "👻", "👽", "👾", "🤖", "😺", "😸", "😹",
|
||||||
|
"😻", "😼", "😽", "🙀", "😿", "😾", "❤️", "🧡",
|
||||||
|
"💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔",
|
||||||
|
"❣️", "💕", "💞", "💓", "💗", "💖", "💘", "💝",
|
||||||
|
"👋", "🤚", "🖐", "✋", "🖖", "👌", "🤏", "✌️",
|
||||||
|
"🤞", "🤟", "🤘", "🤙", "👈", "👉", "👆", "🖕",
|
||||||
|
"👇", "☝️", "👍", "👎", "✊", "👊", "🤛", "🤜",
|
||||||
|
"👏", "🙌", "👐", "🤲", "🤝", "🙏", "✍️", "💅"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(8),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 4.dp),
|
||||||
|
contentPadding = PaddingValues(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
items(emojis) { emoji ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.clickable {
|
||||||
|
onValueChange(value + emoji)
|
||||||
|
}
|
||||||
|
.padding(2.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = emoji,
|
||||||
|
fontSize = 28.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // End of if (!isBlocked) for emoji picker
|
} // End of if (!isBlocked) for emoji picker
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user