feat: Enhance ProtocolManager logging and error handling; optimize emoji picker performance
This commit is contained in:
@@ -285,16 +285,16 @@ fun MainScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔍 Вход в поиск - slide сверху
|
// 🔍 Вход в поиск - slide справа (как чаты)
|
||||||
isEnteringSearch -> {
|
isEnteringSearch -> {
|
||||||
slideInVertically(
|
slideInHorizontally(
|
||||||
initialOffsetY = { -it },
|
initialOffsetX = { fullWidth -> fullWidth }, // Начинаем за экраном справа
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
dampingRatio = Spring.DampingRatioNoBouncy,
|
||||||
stiffness = Spring.StiffnessMediumLow
|
stiffness = Spring.StiffnessMediumLow
|
||||||
)
|
)
|
||||||
) togetherWith slideOutVertically(
|
) togetherWith slideOutHorizontally(
|
||||||
targetOffsetY = { it / 4 },
|
targetOffsetX = { fullWidth -> -fullWidth / 4 }, // Список уходит влево на 25%
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
dampingRatio = Spring.DampingRatioNoBouncy,
|
||||||
stiffness = Spring.StiffnessMediumLow
|
stiffness = Spring.StiffnessMediumLow
|
||||||
@@ -302,16 +302,16 @@ fun MainScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❌ Выход из поиска
|
// ❌ Выход из поиска - обратный slide
|
||||||
isExitingSearch -> {
|
isExitingSearch -> {
|
||||||
slideInVertically(
|
slideInHorizontally(
|
||||||
initialOffsetY = { it / 4 },
|
initialOffsetX = { fullWidth -> -fullWidth / 4 }, // Список возвращается слева
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
dampingRatio = Spring.DampingRatioNoBouncy,
|
||||||
stiffness = Spring.StiffnessMedium
|
stiffness = Spring.StiffnessMedium
|
||||||
)
|
)
|
||||||
) togetherWith slideOutVertically(
|
) togetherWith slideOutHorizontally(
|
||||||
targetOffsetY = { -it },
|
targetOffsetX = { fullWidth -> fullWidth }, // Поиск уходит за экран вправо
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
dampingRatio = Spring.DampingRatioNoBouncy,
|
||||||
stiffness = Spring.StiffnessMedium
|
stiffness = Spring.StiffnessMedium
|
||||||
|
|||||||
@@ -103,10 +103,17 @@ object ProtocolManager {
|
|||||||
addLog("🟢 Online status received: ${onlinePacket.publicKeysState.size} entries")
|
addLog("🟢 Online status received: ${onlinePacket.publicKeysState.size} entries")
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
if (messageRepository == null) {
|
||||||
|
addLog("❌ ERROR: messageRepository is NULL!")
|
||||||
|
} else {
|
||||||
|
addLog("✅ messageRepository is initialized")
|
||||||
onlinePacket.publicKeysState.forEach { item ->
|
onlinePacket.publicKeysState.forEach { item ->
|
||||||
val isOnline = item.state == OnlineState.ONLINE
|
val isOnline = item.state == OnlineState.ONLINE
|
||||||
addLog(" ${item.publicKey.take(16)}... -> ${if (isOnline) "ONLINE" else "OFFLINE"}")
|
addLog(" ${item.publicKey.take(16)}... -> ${if (isOnline) "ONLINE" else "OFFLINE"}")
|
||||||
|
addLog(" Calling updateOnlineStatus...")
|
||||||
messageRepository?.updateOnlineStatus(item.publicKey, isOnline)
|
messageRepository?.updateOnlineStatus(item.publicKey, isOnline)
|
||||||
|
addLog(" updateOnlineStatus called")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,27 +639,32 @@ fun ChatDetailScreen(
|
|||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
showMenu = true
|
showMenu = true
|
||||||
}
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.MoreVert,
|
Icons.Default.MoreVert,
|
||||||
contentDescription = "More",
|
contentDescription = "More",
|
||||||
tint = headerIconColor
|
tint = headerIconColor,
|
||||||
|
modifier = Modifier.size(26.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выпадающее меню - чистый дизайн без артефактов
|
// Выпадающее меню - чистый дизайн без артефактов
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = MaterialTheme.colorScheme.copy(
|
||||||
|
surface = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onDismissRequest = { showMenu = false },
|
onDismissRequest = { showMenu = false },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(220.dp)
|
.width(220.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(
|
.background(
|
||||||
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
|
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
|
||||||
)
|
|
||||||
.shadow(
|
|
||||||
elevation = 8.dp,
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -689,7 +694,10 @@ fun ChatDetailScreen(
|
|||||||
showMenu = false
|
showMenu = false
|
||||||
showDeleteConfirm = true
|
showDeleteConfirm = true
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
colors = MenuDefaults.itemColors(
|
||||||
|
textColor = Color(0xFFE53935)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Разделитель
|
// Разделитель
|
||||||
@@ -733,7 +741,10 @@ fun ChatDetailScreen(
|
|||||||
showBlockConfirm = true
|
showBlockConfirm = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
colors = MenuDefaults.itemColors(
|
||||||
|
textColor = PrimaryBlue
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Разделитель
|
// Разделитель
|
||||||
@@ -771,8 +782,12 @@ fun ChatDetailScreen(
|
|||||||
showMenu = false
|
showMenu = false
|
||||||
showLogs = true
|
showLogs = true
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
colors = MenuDefaults.itemColors(
|
||||||
|
textColor = textColor
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,10 +812,16 @@ fun ChatDetailScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.graphicsLayer { clip = false }
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
// Список сообщений - динамический padding для клавиатуры/эмодзи
|
// Список сообщений - динамический padding для клавиатуры/эмодзи
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(bottom = listBottomPadding)) {
|
// 🔥 graphicsLayer(clip = false) - позволяет пузырькам выходить за границы padding
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.graphicsLayer { clip = false }
|
||||||
|
.padding(bottom = listBottomPadding)
|
||||||
|
) {
|
||||||
when {
|
when {
|
||||||
// 🔥 СКЕЛЕТОН - показываем пока загружаются сообщения
|
// 🔥 СКЕЛЕТОН - показываем пока загружаются сообщения
|
||||||
isLoading -> {
|
isLoading -> {
|
||||||
@@ -1483,11 +1504,12 @@ private fun MessageBubble(
|
|||||||
label = "selectionAlpha"
|
label = "selectionAlpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🔥 ОПТИМИЗАЦИЯ: Кешируем цвета - они не меняются для одного сообщения
|
// 🔥 Цвета - НАШИ ОРИГИНАЛЬНЫЕ
|
||||||
val bubbleColor = remember(message.isOutgoing, isDarkTheme) {
|
val bubbleColor = remember(message.isOutgoing, isDarkTheme) {
|
||||||
if (message.isOutgoing) {
|
if (message.isOutgoing) {
|
||||||
PrimaryBlue
|
PrimaryBlue // Исходящие - наш синий
|
||||||
} else {
|
} else {
|
||||||
|
// Входящие - наши цвета
|
||||||
if (isDarkTheme) Color(0xFF212121) else Color(0xFFF5F5F5)
|
if (isDarkTheme) Color(0xFF212121) else Color(0xFFF5F5F5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1495,18 +1517,20 @@ private fun MessageBubble(
|
|||||||
if (message.isOutgoing) Color.White
|
if (message.isOutgoing) Color.White
|
||||||
else if (isDarkTheme) Color.White else Color(0xFF000000)
|
else if (isDarkTheme) Color.White else Color(0xFF000000)
|
||||||
}
|
}
|
||||||
|
// Время - наши оригинальные цвета
|
||||||
val timeColor = remember(message.isOutgoing, isDarkTheme) {
|
val timeColor = remember(message.isOutgoing, isDarkTheme) {
|
||||||
if (message.isOutgoing) Color.White.copy(alpha = 0.7f)
|
if (message.isOutgoing) Color.White.copy(alpha = 0.7f)
|
||||||
else if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
else if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 ОПТИМИЗАЦИЯ: Кешируем форму bubble
|
// 🔥 TELEGRAM STYLE: Форма пузырька - более мягкие углы
|
||||||
val bubbleShape = remember(message.isOutgoing, showTail) {
|
val bubbleShape = remember(message.isOutgoing, showTail) {
|
||||||
RoundedCornerShape(
|
RoundedCornerShape(
|
||||||
topStart = 18.dp,
|
topStart = 16.dp,
|
||||||
topEnd = 18.dp,
|
topEnd = 16.dp,
|
||||||
bottomStart = if (message.isOutgoing) 18.dp else (if (showTail) 4.dp else 18.dp),
|
// Хвостик: маленький радиус (4dp) только у нижнего угла со стороны отправителя
|
||||||
bottomEnd = if (message.isOutgoing) (if (showTail) 4.dp else 18.dp) else 18.dp
|
bottomStart = if (message.isOutgoing) 16.dp else (if (showTail) 4.dp else 16.dp),
|
||||||
|
bottomEnd = if (message.isOutgoing) (if (showTail) 4.dp else 16.dp) else 16.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1574,11 +1598,10 @@ private fun MessageBubble(
|
|||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp, vertical = 1.dp)
|
// 🔥 TELEGRAM: horizontal 4dp (меньше), vertical 2dp (компактнее)
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
.offset { IntOffset(animatedOffset.toInt(), 0) }
|
.offset { IntOffset(animatedOffset.toInt(), 0) }
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
// ❌ УБРАЛИ: alpha = alpha * selectionAlpha и translationY
|
|
||||||
// Оставляем только selection анимацию
|
|
||||||
this.alpha = selectionAlpha
|
this.alpha = selectionAlpha
|
||||||
this.scaleX = selectionScale
|
this.scaleX = selectionScale
|
||||||
this.scaleY = selectionScale
|
this.scaleY = selectionScale
|
||||||
@@ -1607,7 +1630,7 @@ private fun MessageBubble(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.widthIn(max = 300.dp)
|
Modifier.widthIn(max = 280.dp) // 🔥 TELEGRAM: чуть уже (280dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
@@ -1616,9 +1639,10 @@ private fun MessageBubble(
|
|||||||
)
|
)
|
||||||
.clip(bubbleShape)
|
.clip(bubbleShape)
|
||||||
.background(bubbleColor)
|
.background(bubbleColor)
|
||||||
.padding(horizontal = 12.dp, vertical = 7.dp)
|
// 🔥 TELEGRAM: padding 10-12dp horizontal, 8dp vertical
|
||||||
|
.padding(horizontal = 10.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
// 🔥 Telegram-style: текст и время на одной строке, выровнены по нижней границе
|
// 🔥 TELEGRAM STYLE: текст и время на одной строке
|
||||||
Column {
|
Column {
|
||||||
// Reply bubble (цитата)
|
// Reply bubble (цитата)
|
||||||
message.replyData?.let { reply ->
|
message.replyData?.let { reply ->
|
||||||
@@ -1627,32 +1651,34 @@ private fun MessageBubble(
|
|||||||
isOutgoing = message.isOutgoing,
|
isOutgoing = message.isOutgoing,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(4.dp)) // Меньше отступ
|
||||||
}
|
}
|
||||||
|
|
||||||
// Текст и время в одной строке (Row)
|
// Текст и время в одной строке (Row)
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.Bottom, // Выравнивание по нижней границе (baseline)
|
verticalAlignment = Alignment.Bottom,
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp) // Чуть больше отступ до времени
|
||||||
) {
|
) {
|
||||||
// Текст (не растягивается на всю ширину)
|
// 🔥 TELEGRAM: Текст 17sp, lineHeight 22sp, letterSpacing -0.4sp
|
||||||
AppleEmojiText(
|
AppleEmojiText(
|
||||||
text = message.text,
|
text = message.text,
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = 16.sp,
|
fontSize = 17.sp,
|
||||||
modifier = Modifier.weight(1f, fill = false)
|
modifier = Modifier.weight(1f, fill = false)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Время и статус справа
|
// Время и статус справа
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(3.dp),
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
modifier = Modifier.padding(bottom = 1.dp) // Небольшая коррекция для выравнивания
|
modifier = Modifier.padding(bottom = 2.dp)
|
||||||
) {
|
) {
|
||||||
|
// 🔥 TELEGRAM: Время 11sp, italic style
|
||||||
Text(
|
Text(
|
||||||
text = timeFormat.format(message.timestamp),
|
text = timeFormat.format(message.timestamp),
|
||||||
color = timeColor,
|
color = timeColor,
|
||||||
fontSize = 11.sp
|
fontSize = 11.sp,
|
||||||
|
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic
|
||||||
)
|
)
|
||||||
if (message.isOutgoing) {
|
if (message.isOutgoing) {
|
||||||
AnimatedMessageStatus(
|
AnimatedMessageStatus(
|
||||||
@@ -1781,7 +1807,7 @@ private fun AnimatedMessageStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🔥 Reply bubble (цитата) внутри сообщения - как в React Native
|
* 🔥 Reply bubble (цитата) внутри сообщения
|
||||||
* Стиль: вертикальная линия слева + имя + текст
|
* Стиль: вертикальная линия слева + имя + текст
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1790,7 +1816,7 @@ private fun ReplyBubble(
|
|||||||
isOutgoing: Boolean,
|
isOutgoing: Boolean,
|
||||||
isDarkTheme: Boolean
|
isDarkTheme: Boolean
|
||||||
) {
|
) {
|
||||||
// Цвета как в React Native
|
// НАШИ ОРИГИНАЛЬНЫЕ ЦВЕТА
|
||||||
val backgroundColor = if (isOutgoing) {
|
val backgroundColor = if (isOutgoing) {
|
||||||
Color.Black.copy(alpha = 0.15f)
|
Color.Black.copy(alpha = 0.15f)
|
||||||
} else {
|
} else {
|
||||||
@@ -1809,7 +1835,7 @@ private fun ReplyBubble(
|
|||||||
PrimaryBlue
|
PrimaryBlue
|
||||||
}
|
}
|
||||||
|
|
||||||
val textColor = if (isOutgoing) {
|
val replyTextColor = if (isOutgoing) {
|
||||||
Color.White.copy(alpha = 0.85f)
|
Color.White.copy(alpha = 0.85f)
|
||||||
} else {
|
} else {
|
||||||
if (isDarkTheme) Color.White else Color.Black
|
if (isDarkTheme) Color.White else Color.Black
|
||||||
@@ -1817,12 +1843,12 @@ private fun ReplyBubble(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.wrapContentWidth()
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
) {
|
) {
|
||||||
// Вертикальная линия слева (как borderLeft в React Native)
|
// 🔥 TELEGRAM: Вертикальная линия слева 3dp
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(3.dp)
|
.width(3.dp)
|
||||||
@@ -1833,22 +1859,24 @@ private fun ReplyBubble(
|
|||||||
// Контент
|
// Контент
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 10.dp, end = 10.dp, top = 6.dp, bottom = 6.dp)
|
// 🔥 TELEGRAM: padding как в дизайне
|
||||||
|
.padding(start = 8.dp, end = 10.dp, top = 4.dp, bottom = 4.dp)
|
||||||
|
.widthIn(max = 220.dp)
|
||||||
) {
|
) {
|
||||||
// Имя отправителя цитируемого сообщения
|
// 🔥 TELEGRAM: Имя 14sp, Medium weight
|
||||||
Text(
|
Text(
|
||||||
text = replyData.senderName,
|
text = replyData.senderName,
|
||||||
color = nameColor,
|
color = nameColor,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
// Текст цитируемого сообщения
|
// 🔥 TELEGRAM: Текст цитаты 14sp, Regular
|
||||||
Text(
|
Text(
|
||||||
text = replyData.text.ifEmpty { "..." },
|
text = replyData.text.ifEmpty { "..." },
|
||||||
color = textColor,
|
color = replyTextColor,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
@@ -2007,6 +2035,7 @@ private fun MessageInputBar(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.graphicsLayer { clip = false }
|
||||||
.windowInsetsPadding(WindowInsets.ime.only(WindowInsetsSides.Bottom))
|
.windowInsetsPadding(WindowInsets.ime.only(WindowInsetsSides.Bottom))
|
||||||
) {
|
) {
|
||||||
// Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
|
// Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
|
||||||
@@ -2053,6 +2082,7 @@ private fun MessageInputBar(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.graphicsLayer { clip = false }
|
||||||
) {
|
) {
|
||||||
// Верхний border (как в архиве)
|
// Верхний border (как в архиве)
|
||||||
Box(
|
Box(
|
||||||
@@ -2240,8 +2270,8 @@ private fun MessageInputBar(
|
|||||||
// Мгновенно когда клавиатура открывается
|
// Мгновенно когда клавиатура открывается
|
||||||
snap()
|
snap()
|
||||||
} else {
|
} else {
|
||||||
// Быстрая анимация для мгновенного отклика (как в Telegram)
|
// Быстрая анимация как обычная клавиатура (200ms)
|
||||||
tween(durationMillis = 150, easing = TelegramEasing)
|
tween(durationMillis = 200, easing = FastOutSlowInEasing)
|
||||||
},
|
},
|
||||||
label = "EmojiPanelHeight"
|
label = "EmojiPanelHeight"
|
||||||
)
|
)
|
||||||
@@ -2250,10 +2280,9 @@ private fun MessageInputBar(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(animatedHeight)
|
.height(animatedHeight)
|
||||||
.clipToBounds()
|
|
||||||
) {
|
) {
|
||||||
// 🚀 Рендерим панель только когда нужно
|
// 🚀 Рендерим панель только когда нужно
|
||||||
if (showEmojiPicker && !isKeyboardVisible && animatedHeight > 0.dp) {
|
if (showEmojiPicker && !isKeyboardVisible) {
|
||||||
AppleEmojiPickerPanel(
|
AppleEmojiPickerPanel(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
onEmojiSelected = { emoji ->
|
onEmojiSelected = { emoji ->
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ 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.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -505,14 +506,8 @@ fun EmojiButton(
|
|||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isPressed by interactionSource.collectIsPressedAsState()
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
val scale by animateFloatAsState(
|
// 🚀 Убираем анимацию scale для производительности
|
||||||
targetValue = if (isPressed) 0.85f else 1f,
|
val scale = if (isPressed) 0.85f else 1f
|
||||||
animationSpec = spring(
|
|
||||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
|
||||||
stiffness = Spring.StiffnessHigh
|
|
||||||
),
|
|
||||||
label = "emojiScale"
|
|
||||||
)
|
|
||||||
|
|
||||||
val imageRequest = remember(unified) {
|
val imageRequest = remember(unified) {
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
@@ -613,12 +608,16 @@ fun AppleEmojiPickerPanel(
|
|||||||
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
|
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
|
||||||
val gridState = rememberLazyGridState()
|
val gridState = rememberLazyGridState()
|
||||||
|
|
||||||
// Загружаем эмодзи если еще не загружены (синхронно из кеша если уже загружено)
|
// 🚀 Предзагружаем эмодзи СИНХРОННО при создании компонента
|
||||||
LaunchedEffect(Unit) {
|
val emojisReady = remember {
|
||||||
if (!EmojiCache.isLoaded) {
|
if (!EmojiCache.isLoaded) {
|
||||||
|
// Загружаем синхронно для мгновенного отображения
|
||||||
|
runBlocking {
|
||||||
EmojiCache.loadEmojis(context)
|
EmojiCache.loadEmojis(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
// Текущие эмодзи для выбранной категории - используем derivedStateOf для оптимизации
|
// Текущие эмодзи для выбранной категории - используем derivedStateOf для оптимизации
|
||||||
val currentEmojis by remember {
|
val currentEmojis by remember {
|
||||||
@@ -631,7 +630,7 @@ fun AppleEmojiPickerPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сбрасываем скролл при смене категории
|
// 🚀 Убираем анимацию скролла для мгновенного переключения категорий
|
||||||
LaunchedEffect(selectedCategory) {
|
LaunchedEffect(selectedCategory) {
|
||||||
gridState.scrollToItem(0)
|
gridState.scrollToItem(0)
|
||||||
}
|
}
|
||||||
@@ -700,6 +699,7 @@ fun AppleEmojiPickerPanel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 🚀 Оптимизированная LazyVerticalGrid для быстрого рендеринга
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
state = gridState,
|
state = gridState,
|
||||||
columns = GridCells.Fixed(8),
|
columns = GridCells.Fixed(8),
|
||||||
|
|||||||
Reference in New Issue
Block a user