feat: Refactor MessageInputBar for improved emoji picker and input field design

This commit is contained in:
k1ngsterr1
2026-01-12 22:41:00 +05:00
parent dda07d80af
commit b8a2334042

View File

@@ -1,28 +1,31 @@
package com.rosetta.messenger.ui.components package com.rosetta.messenger.ui.components
import android.content.Context import android.content.Context
import android.util.Log
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 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.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -31,152 +34,183 @@ import coil.compose.AsyncImage
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/** /**
* Apple Emoji Picker с PNG изображениями * 🍎 Apple Emoji Picker с PNG изображениями
* Загружает ВСЕ эмодзи из assets/emoji директории * - Категория "Все" первая
* - Apple Style эмодзи из assets/emoji
* - Фоновая загрузка для оптимизации
*/ */
// Категории эмодзи // Категории эмодзи с Material иконками
data class EmojiCategory( data class EmojiCategory(
val key: String, val key: String,
val label: String, // unified код для иконки категории val label: String,
val ranges: List<Pair<Int, Int>> // Unicode диапазоны для этой категории val icon: ImageVector,
val ranges: List<Pair<Int, Int>>
) )
// Стандартный порядок категорий как в iOS/Android/WhatsApp // Порядок категорий: "All" первая, затем стандартные
val EMOJI_CATEGORIES = listOf( val EMOJI_CATEGORIES = listOf(
// 😀 Smileys & Emotion: лица, эмоции, руки // 🔥 ALL - все эмодзи (первая категория)
EmojiCategory("Smileys", "1f600", listOf( EmojiCategory("All", "Все", Icons.Default.Apps, emptyList()),
0x1F600 to 0x1F64F, // Emoticons // 😀 Smileys & Emotion
0x1F910 to 0x1F92F, // Face with... EmojiCategory("Smileys", "Смайлы", Icons.Default.SentimentSatisfied, listOf(
0x1F970 to 0x1F9FF, // More faces 0x1F600 to 0x1F64F,
0x263A to 0x263A, // ☺ 0x1F910 to 0x1F92F,
0x2639 to 0x2639 // ☹ 0x1F970 to 0x1F9FF,
0x263A to 0x263A,
0x2639 to 0x2639
)), )),
// 👋 People & Body: люди, жесты, части тела // 👋 People & Body
EmojiCategory("People", "1f44b", listOf( EmojiCategory("People", "Люди", Icons.Default.Person, listOf(
0x1F466 to 0x1F4FF, // People 0x1F466 to 0x1F4FF,
0x1F9D0 to 0x1F9DF, // More people 0x1F9D0 to 0x1F9DF,
0x270A to 0x270D, // Hands 0x270A to 0x270D,
0x261D to 0x261D, // ☝ 0x261D to 0x261D,
0x1F440 to 0x1F465 // Body parts 0x1F440 to 0x1F465
)), )),
// 🐱 Animals & Nature: животные, растения, природа // 🐱 Animals & Nature
EmojiCategory("Animals", "1f431", listOf( EmojiCategory("Animals", "Животные", Icons.Default.Pets, listOf(
0x1F400 to 0x1F43F, // Animals 0x1F400 to 0x1F43F,
0x1F980 to 0x1F9AE, // More animals 0x1F980 to 0x1F9AE,
0x1F330 to 0x1F335, // Plants 0x1F330 to 0x1F335,
0x1F337 to 0x1F34F, // Flowers, fruits 0x1F337 to 0x1F34F,
0x2618 to 0x2618 // ☘ 0x2618 to 0x2618
)), )),
// 🍎 Food & Drink: еда, напитки // 🍎 Food & Drink
EmojiCategory("Food", "1f34e", listOf( EmojiCategory("Food", "Еда", Icons.Default.Restaurant, listOf(
0x1F345 to 0x1F37F, // Food 0x1F345 to 0x1F37F,
0x1F950 to 0x1F96F, // More food 0x1F950 to 0x1F96F,
0x1F9C0 to 0x1F9CB, // Cheese, drinks 0x1F9C0 to 0x1F9CB,
0x1FAD0 to 0x1FAD9, // New food 0x1FAD0 to 0x1FAD9,
0x2615 to 0x2615 // ☕ 0x2615 to 0x2615
)), )),
// ✈️ Travel & Places: транспорт, места, здания // ✈️ Travel & Places
EmojiCategory("Travel", "2708-fe0f", listOf( EmojiCategory("Travel", "Места", Icons.Default.Flight, listOf(
0x1F680 to 0x1F6FF, // Transport 0x1F680 to 0x1F6FF,
0x1F3D4 to 0x1F3DF, // Places 0x1F3D4 to 0x1F3DF,
0x1F3E0 to 0x1F3F0, // Buildings 0x1F3E0 to 0x1F3F0,
0x2708 to 0x2708, // ✈ 0x2708 to 0x2708,
0x26F0 to 0x26FF // Mountains, etc 0x26F0 to 0x26FF
)), )),
// ⚽ Activities: спорт, игры, хобби // ⚽ Activities
EmojiCategory("Activities", "26bd", listOf( EmojiCategory("Activities", "Спорт", Icons.Default.SportsSoccer, listOf(
0x1F3A0 to 0x1F3CA, // Activities 0x1F3A0 to 0x1F3CA,
0x1F3CB to 0x1F3D3, // Sports 0x1F3CB to 0x1F3D3,
0x1F93C to 0x1F94F, // More sports 0x1F93C to 0x1F94F,
0x26BD to 0x26BE, // ⚽⚾ 0x26BD to 0x26BE,
0x265F to 0x2660, // Chess 0x265F to 0x2660,
0x1F9E0 to 0x1F9FF // Games 0x1F9E0 to 0x1F9FF
)), )),
// 💡 Objects: предметы, инструменты // 💡 Objects
EmojiCategory("Objects", "1f4a1", listOf( EmojiCategory("Objects", "Объекты", Icons.Default.Lightbulb, listOf(
0x1F4A1 to 0x1F4FF, // Objects (lightbulb to...) 0x1F4A1 to 0x1F4FF,
0x1F500 to 0x1F5FF, // More objects 0x1F500 to 0x1F5FF,
0x1F6E0 to 0x1F6EF, // Tools 0x1F6E0 to 0x1F6EF,
0x1FA70 to 0x1FAFF, // New objects 0x1FA70 to 0x1FAFF,
0x2328 to 0x2328 // ⌨ 0x2328 to 0x2328
)), )),
// ❤️ Symbols: сердца, знаки, символы // ❤️ Symbols
EmojiCategory("Symbols", "2764-fe0f", listOf( EmojiCategory("Symbols", "Символы", Icons.Default.Favorite, listOf(
0x2764 to 0x2764, // ❤ 0x2764 to 0x2764,
0x1F490 to 0x1F49F, // Hearts 0x1F490 to 0x1F49F,
0x2600 to 0x26FF, // Misc symbols 0x2600 to 0x26FF,
0x2700 to 0x27BF, // Dingbats 0x2700 to 0x27BF,
0x1F170 to 0x1F1FF, // Squared letters (before flags) 0x1F170 to 0x1F1FF,
0x00A9 to 0x00AE, // ©® 0x00A9 to 0x00AE,
0x203C to 0x3299 // Misc 0x203C to 0x3299
)), )),
// 🏳️ Flags: флаги стран // 🏳️ Flags
EmojiCategory("Flags", "1f3f3-fe0f", listOf( EmojiCategory("Flags", "Флаги", Icons.Default.Flag, listOf(
0x1F1E0 to 0x1F1FF, // Regional indicators (flags) 0x1F1E0 to 0x1F1FF,
0x1F3F3 to 0x1F3F4, // 🏳🏴 0x1F3F3 to 0x1F3F4,
0x1F3C1 to 0x1F3C1, // 🏁 0x1F3C1 to 0x1F3C1,
0x1F6A9 to 0x1F6A9 // 🚩 0x1F6A9 to 0x1F6A9
)) ))
) )
/** /**
* Проверяет, попадает ли emoji в диапазон категории * Проверяет, попадает ли emoji в диапазон категории
*/ */
fun emojiMatchesCategory(emoji: String, category: EmojiCategory): Boolean { private fun emojiMatchesCategory(emoji: String, category: EmojiCategory): Boolean {
val unified = emoji.lowercase().split("-").firstOrNull() ?: return false val unified = emoji.lowercase().split("-").firstOrNull() ?: return false
val codePoint = try { unified.toInt(16) } catch (e: Exception) { return false } val codePoint = try { unified.toInt(16) } catch (e: Exception) { return false }
return category.ranges.any { (start, end) -> codePoint in start..end } return category.ranges.any { (start, end) -> codePoint in start..end }
} }
/** /**
* Загружает все эмодзи из assets/emoji * Синглтон для кэширования эмодзи
*/ */
fun loadAllEmojisFromAssets(context: Context): List<String> { object EmojiCache {
return try { private var allEmojis: List<String>? = null
context.assets.list("emoji") private var emojisByCategory: Map<String, List<String>>? = null
?.filter { it.endsWith(".png") } var isLoaded by mutableStateOf(false)
?.map { it.removeSuffix(".png") } private set
?.sorted()
?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
/**
* Группирует эмодзи по категориям на основе Unicode диапазонов
*/
fun groupEmojisByCategory(allEmojis: List<String>): Map<String, List<String>> {
val result = mutableMapOf<String, MutableList<String>>()
val usedEmojis = mutableSetOf<String>()
// Инициализируем категории suspend fun loadEmojis(context: Context) {
EMOJI_CATEGORIES.forEach { category -> if (allEmojis != null) {
result[category.key] = mutableListOf() isLoaded = true
} return
}
// Группируем по Unicode диапазонам
for (emoji in allEmojis) { withContext(Dispatchers.IO) {
for (category in EMOJI_CATEGORIES) { try {
if (emojiMatchesCategory(emoji, category) && emoji !in usedEmojis) { val emojis = context.assets.list("emoji")
result[category.key]?.add(emoji) ?.filter { it.endsWith(".png") }
usedEmojis.add(emoji) ?.map { it.removeSuffix(".png") }
break ?.sorted()
?: emptyList()
allEmojis = emojis
emojisByCategory = groupEmojis(emojis)
Log.d("EmojiCache", "Loaded ${emojis.size} emojis")
} catch (e: Exception) {
Log.e("EmojiCache", "Error loading emojis", e)
allEmojis = emptyList()
emojisByCategory = emptyMap()
} }
} }
isLoaded = true
} }
// Добавляем неклассифицированные в Symbols fun getEmojisForCategory(categoryKey: String): List<String> {
for (emoji in allEmojis) { return if (categoryKey == "All") {
if (emoji !in usedEmojis) { allEmojis ?: emptyList()
result["Symbols"]?.add(emoji) } else {
emojisByCategory?.get(categoryKey) ?: emptyList()
} }
} }
return result private fun groupEmojis(allEmojis: List<String>): Map<String, List<String>> {
val result = mutableMapOf<String, MutableList<String>>()
val usedEmojis = mutableSetOf<String>()
EMOJI_CATEGORIES.filter { it.key != "All" }.forEach { category ->
result[category.key] = mutableListOf()
}
for (emoji in allEmojis) {
for (category in EMOJI_CATEGORIES) {
if (category.key == "All") continue
if (emojiMatchesCategory(emoji, category) && emoji !in usedEmojis) {
result[category.key]?.add(emoji)
usedEmojis.add(emoji)
break
}
}
}
for (emoji in allEmojis) {
if (emoji !in usedEmojis) {
result["Symbols"]?.add(emoji)
}
}
return result
}
} }
/** /**
@@ -196,8 +230,7 @@ fun unifiedToEmoji(unified: String): String {
} }
/** /**
* Кнопка эмодзи с PNG изображением и анимацией нажатия * Кнопка эмодзи с PNG изображением
* Оптимизирована с кэшированием
*/ */
@Composable @Composable
fun EmojiButton( fun EmojiButton(
@@ -218,16 +251,15 @@ fun EmojiButton(
label = "emojiScale" label = "emojiScale"
) )
// Мемоизируем ImageRequest для оптимизации - используем assetFile fetcher
val imageRequest = remember(unified, context) { val imageRequest = remember(unified, context) {
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data("file:///android_asset/emoji/${unified.lowercase()}.png") .data("file:///android_asset/emoji/${unified.lowercase()}.png")
.crossfade(false) .crossfade(false)
.size(64) // Задаём размер для оптимизации памяти .size(64)
.memoryCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED) .diskCachePolicy(CachePolicy.ENABLED)
.memoryCacheKey("emoji_${unified}") .memoryCacheKey("emoji_$unified")
.diskCacheKey("emoji_${unified}") .diskCacheKey("emoji_$unified")
.build() .build()
} }
@@ -247,14 +279,14 @@ fun EmojiButton(
AsyncImage( AsyncImage(
model = imageRequest, model = imageRequest,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp), modifier = Modifier.size(28.dp),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
} }
} }
/** /**
* Кнопка категории с PNG изображением * Кнопка категории с Material иконкой
*/ */
@Composable @Composable
fun CategoryButton( fun CategoryButton(
@@ -264,7 +296,6 @@ fun CategoryButton(
isDarkTheme: Boolean = true, isDarkTheme: Boolean = true,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState() val isPressed by interactionSource.collectIsPressedAsState()
@@ -277,27 +308,17 @@ fun CategoryButton(
label = "categoryScale" label = "categoryScale"
) )
// Мемоизируем ImageRequest с размером для оптимизации val backgroundColor = if (isSelected) PrimaryBlue.copy(alpha = 0.2f) else Color.Transparent
val imageRequest = remember(category.label, context) { val iconTint = if (isSelected) PrimaryBlue
ImageRequest.Builder(context) else if (isDarkTheme) Color.White.copy(alpha = 0.6f)
.data("file:///android_asset/emoji/${category.label.lowercase()}.png") else Color.Black.copy(alpha = 0.5f)
.crossfade(false)
.size(48)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCacheKey("category_${category.label}")
.diskCacheKey("category_${category.label}")
.build()
}
Box( Box(
modifier = modifier modifier = modifier
.size(40.dp) .size(40.dp)
.scale(scale) .scale(scale)
.clip(CircleShape) .clip(CircleShape)
.background( .background(backgroundColor)
if (isSelected) PrimaryBlue.copy(alpha = 0.25f) else Color.Transparent
)
.clickable( .clickable(
interactionSource = interactionSource, interactionSource = interactionSource,
indication = null, indication = null,
@@ -305,18 +326,17 @@ fun CategoryButton(
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
AsyncImage( Icon(
model = imageRequest, imageVector = category.icon,
contentDescription = category.key, contentDescription = category.label,
modifier = Modifier.size(24.dp), tint = iconTint,
contentScale = ContentScale.Fit modifier = Modifier.size(22.dp)
) )
} }
} }
/** /**
* Apple Emoji Picker Panel - Liquid Glass стиль * Apple Emoji Picker Panel
* Загружает ВСЕ эмодзи из assets
*/ */
@Composable @Composable
fun AppleEmojiPickerPanel( fun AppleEmojiPickerPanel(
@@ -326,22 +346,21 @@ fun AppleEmojiPickerPanel(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current val context = LocalContext.current
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) } var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) } // "All" по умолчанию
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
// Загружаем ВСЕ эмодзи из assets один раз // Загружаем эмодзи в фоне
val allEmojis = remember { LaunchedEffect(Unit) {
loadAllEmojisFromAssets(context) EmojiCache.loadEmojis(context)
}
// Группируем по категориям
val emojisByCategory = remember(allEmojis) {
groupEmojisByCategory(allEmojis)
} }
// Текущие эмодзи для выбранной категории // Текущие эмодзи для выбранной категории
val currentEmojis = remember(selectedCategory.key, emojisByCategory) { val currentEmojis = remember(selectedCategory.key, EmojiCache.isLoaded) {
emojisByCategory[selectedCategory.key] ?: emptyList() if (EmojiCache.isLoaded) {
EmojiCache.getEmojisForCategory(selectedCategory.key)
} else {
emptyList()
}
} }
// Сбрасываем скролл при смене категории // Сбрасываем скролл при смене категории
@@ -349,94 +368,25 @@ fun AppleEmojiPickerPanel(
gridState.scrollToItem(0) gridState.scrollToItem(0)
} }
// Liquid Glass цвета val panelBackground = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7)
val glassBackground = if (isDarkTheme) { val categoryBarBackground = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White
Brush.verticalGradient( val dividerColor = if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f)
colors = listOf(
Color(0xFF2D2D2F).copy(alpha = 0.95f),
Color(0xFF1C1C1E).copy(alpha = 0.98f)
)
)
} else {
Brush.verticalGradient(
colors = listOf(
Color(0xFFF2F2F7).copy(alpha = 0.96f),
Color(0xFFE5E5EA).copy(alpha = 0.98f)
)
)
}
val glassBorder = if (isDarkTheme) {
Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = 0.15f),
Color.White.copy(alpha = 0.05f)
)
)
} else {
Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = 0.9f),
Color.Black.copy(alpha = 0.05f)
)
)
}
val categoryBarBackground = if (isDarkTheme) Color(0xFF1A1A1C).copy(alpha = 0.9f)
else Color.White.copy(alpha = 0.85f)
val dividerColor = if (isDarkTheme) Color.White.copy(alpha = 0.08f)
else Color.Black.copy(alpha = 0.06f)
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.height(300.dp) .background(panelBackground)
.shadow(
elevation = 8.dp,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
clip = false,
ambientColor = Color.Black.copy(alpha = 0.3f),
spotColor = Color.Black.copy(alpha = 0.3f)
)
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(glassBackground)
.border(
width = 1.dp,
brush = glassBorder,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
)
) { ) {
// Ручка для свайпа (как в iOS) // Категории - горизонтальный скролл
Box( LazyRow(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(36.dp)
.height(4.dp)
.clip(RoundedCornerShape(2.dp))
.background(
if (isDarkTheme) Color.White.copy(alpha = 0.3f)
else Color.Black.copy(alpha = 0.2f)
)
)
}
// Категории сверху - тоже в glass стиле
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp)
.clip(RoundedCornerShape(16.dp))
.background(categoryBarBackground) .background(categoryBarBackground)
.padding(horizontal = 4.dp, vertical = 6.dp), .padding(horizontal = 8.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
EMOJI_CATEGORIES.forEach { category -> items(EMOJI_CATEGORIES) { category ->
CategoryButton( CategoryButton(
category = category, category = category,
isSelected = selectedCategory == category, isSelected = selectedCategory == category,
@@ -446,33 +396,63 @@ fun AppleEmojiPickerPanel(
} }
} }
Spacer(modifier = Modifier.height(8.dp)) // Разделитель
Box(
// Сетка эмодзи с оптимизированной загрузкой
LazyVerticalGrid(
state = gridState,
columns = GridCells.Fixed(9),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .height(0.5.dp)
.padding(horizontal = 8.dp), .background(dividerColor)
horizontalArrangement = Arrangement.spacedBy(2.dp), )
verticalArrangement = Arrangement.spacedBy(2.dp)
) { // Сетка эмодзи
items( if (!EmojiCache.isLoaded) {
items = currentEmojis, Box(
key = { it } // Используем unified как ключ для оптимизации modifier = Modifier
) { unified -> .fillMaxWidth()
EmojiButton( .weight(1f),
unified = unified, contentAlignment = Alignment.Center
onClick = { emoji -> ) {
onEmojiSelected(emoji) CircularProgressIndicator(
} modifier = Modifier.size(32.dp),
color = PrimaryBlue,
strokeWidth = 2.dp
) )
} }
} else if (currentEmojis.isEmpty()) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
) {
Text(
text = "Нет эмодзи",
color = if (isDarkTheme) Color.White.copy(alpha = 0.5f) else Color.Black.copy(alpha = 0.4f),
fontSize = 14.sp
)
}
} else {
LazyVerticalGrid(
state = gridState,
columns = GridCells.Fixed(8),
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 4.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = PaddingValues(vertical = 8.dp)
) {
items(
items = currentEmojis,
key = { it }
) { unified ->
EmojiButton(
unified = unified,
onClick = { emoji -> onEmojiSelected(emoji) }
)
}
}
} }
// Отступ снизу для navigation bar
Spacer(modifier = Modifier.height(8.dp))
} }
} }