feat: Optimize AppleEmojiPicker with caching and enhance UI with Liquid Glass style

This commit is contained in:
k1ngsterr1
2026-01-12 02:09:56 +05:00
parent 5f348f329e
commit 7bac22850e
2 changed files with 214 additions and 62 deletions

View File

@@ -3,6 +3,7 @@ package com.rosetta.messenger.ui.components
import android.content.Context
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
@@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.*
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.rememberLazyGridState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
@@ -18,12 +20,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
@@ -147,6 +152,7 @@ fun unifiedToEmoji(unified: String): String {
/**
* Кнопка эмодзи с PNG изображением и анимацией нажатия
* Оптимизирована с кэшированием
*/
@Composable
fun EmojiButton(
@@ -167,6 +173,19 @@ fun EmojiButton(
label = "emojiScale"
)
// Мемоизируем ImageRequest для оптимизации - используем assetFile fetcher
val imageRequest = remember(unified, context) {
ImageRequest.Builder(context)
.data("file:///android_asset/emoji/${unified.lowercase()}.png")
.crossfade(false)
.size(64) // Задаём размер для оптимизации памяти
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCacheKey("emoji_${unified}")
.diskCacheKey("emoji_${unified}")
.build()
}
Box(
modifier = modifier
.aspectRatio(1f)
@@ -181,10 +200,7 @@ fun EmojiButton(
contentAlignment = Alignment.Center
) {
AsyncImage(
model = ImageRequest.Builder(context)
.data("file:///android_asset/emoji/${unified.lowercase()}.png")
.crossfade(false)
.build(),
model = imageRequest,
contentDescription = null,
modifier = Modifier.size(32.dp),
contentScale = ContentScale.Fit
@@ -200,6 +216,7 @@ fun CategoryButton(
category: EmojiCategory,
isSelected: Boolean,
onClick: () -> Unit,
isDarkTheme: Boolean = true,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
@@ -215,13 +232,26 @@ fun CategoryButton(
label = "categoryScale"
)
// Мемоизируем ImageRequest с размером для оптимизации
val imageRequest = remember(category.label, context) {
ImageRequest.Builder(context)
.data("file:///android_asset/emoji/${category.label.lowercase()}.png")
.crossfade(false)
.size(48)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCacheKey("category_${category.label}")
.diskCacheKey("category_${category.label}")
.build()
}
Box(
modifier = modifier
.size(40.dp)
.scale(scale)
.clip(CircleShape)
.background(
if (isSelected) PrimaryBlue.copy(alpha = 0.2f) else Color.Transparent
if (isSelected) PrimaryBlue.copy(alpha = 0.25f) else Color.Transparent
)
.clickable(
interactionSource = interactionSource,
@@ -231,10 +261,7 @@ fun CategoryButton(
contentAlignment = Alignment.Center
) {
AsyncImage(
model = ImageRequest.Builder(context)
.data("file:///android_asset/emoji/${category.label.lowercase()}.png")
.crossfade(false)
.build(),
model = imageRequest,
contentDescription = category.key,
modifier = Modifier.size(24.dp),
contentScale = ContentScale.Fit
@@ -243,7 +270,7 @@ fun CategoryButton(
}
/**
* Apple Emoji Picker Panel
* Apple Emoji Picker Panel - Liquid Glass стиль
*/
@Composable
fun AppleEmojiPickerPanel(
@@ -253,25 +280,102 @@ fun AppleEmojiPickerPanel(
modifier: Modifier = Modifier
) {
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
val gridState = rememberLazyGridState()
val panelBackground = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF8F8FA)
val categoryBackground = if (isDarkTheme) Color(0xFF2A2A2C) else Color(0xFFFFFFFF)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE5E5EA)
// Мемоизируем текущие эмодзи
val currentEmojis = remember(selectedCategory.key) {
EMOJIS_BY_CATEGORY[selectedCategory.key] ?: emptyList()
}
val currentEmojis = EMOJIS_BY_CATEGORY[selectedCategory.key] ?: emptyList()
// Сбрасываем скролл при смене категории
LaunchedEffect(selectedCategory) {
gridState.scrollToItem(0)
}
// Liquid Glass цвета
val glassBackground = if (isDarkTheme) {
Brush.verticalGradient(
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(
modifier = modifier
.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(
modifier = Modifier
.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()
.background(categoryBackground)
.padding(horizontal = 8.dp, vertical = 6.dp),
.padding(horizontal = 12.dp)
.clip(RoundedCornerShape(16.dp))
.background(categoryBarBackground)
.padding(horizontal = 4.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
@@ -279,27 +383,29 @@ fun AppleEmojiPickerPanel(
CategoryButton(
category = category,
isSelected = selectedCategory == category,
onClick = { selectedCategory = category }
onClick = { selectedCategory = category },
isDarkTheme = isDarkTheme
)
}
}
Divider(
color = dividerColor,
thickness = 0.5.dp
)
Spacer(modifier = Modifier.height(8.dp))
// Сетка эмодзи
// Сетка эмодзи с оптимизированной загрузкой
LazyVerticalGrid(
state = gridState,
columns = GridCells.Fixed(9),
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(horizontal = 8.dp, vertical = 8.dp),
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
items(currentEmojis) { unified ->
items(
items = currentEmojis,
key = { it } // Используем unified как ключ для оптимизации
) { unified ->
EmojiButton(
unified = unified,
onClick = { emoji ->
@@ -308,5 +414,8 @@ fun AppleEmojiPickerPanel(
)
}
}
// Отступ снизу для navigation bar
Spacer(modifier = Modifier.height(8.dp))
}
}