feat: Enhance ProtocolManager logging and error handling; optimize emoji picker performance

This commit is contained in:
k1ngsterr1
2026-01-13 20:46:46 +05:00
parent 7c911835ea
commit f155c4d36d
4 changed files with 116 additions and 80 deletions

View File

@@ -285,16 +285,16 @@ fun MainScreen(
)
}
// 🔍 Вход в поиск - slide сверху
// 🔍 Вход в поиск - slide справа (как чаты)
isEnteringSearch -> {
slideInVertically(
initialOffsetY = { -it },
slideInHorizontally(
initialOffsetX = { fullWidth -> fullWidth }, // Начинаем за экраном справа
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMediumLow
)
) togetherWith slideOutVertically(
targetOffsetY = { it / 4 },
) togetherWith slideOutHorizontally(
targetOffsetX = { fullWidth -> -fullWidth / 4 }, // Список уходит влево на 25%
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMediumLow
@@ -302,16 +302,16 @@ fun MainScreen(
)
}
// ❌ Выход из поиска
// ❌ Выход из поиска - обратный slide
isExitingSearch -> {
slideInVertically(
initialOffsetY = { it / 4 },
slideInHorizontally(
initialOffsetX = { fullWidth -> -fullWidth / 4 }, // Список возвращается слева
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
) togetherWith slideOutVertically(
targetOffsetY = { -it },
) togetherWith slideOutHorizontally(
targetOffsetX = { fullWidth -> fullWidth }, // Поиск уходит за экран вправо
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium

View File

@@ -103,10 +103,17 @@ object ProtocolManager {
addLog("🟢 Online status received: ${onlinePacket.publicKeysState.size} entries")
scope.launch {
onlinePacket.publicKeysState.forEach { item ->
val isOnline = item.state == OnlineState.ONLINE
addLog(" ${item.publicKey.take(16)}... -> ${if (isOnline) "ONLINE" else "OFFLINE"}")
messageRepository?.updateOnlineStatus(item.publicKey, isOnline)
if (messageRepository == null) {
addLog("❌ ERROR: messageRepository is NULL!")
} else {
addLog("messageRepository is initialized")
onlinePacket.publicKeysState.forEach { item ->
val isOnline = item.state == OnlineState.ONLINE
addLog(" ${item.publicKey.take(16)}... -> ${if (isOnline) "ONLINE" else "OFFLINE"}")
addLog(" Calling updateOnlineStatus...")
messageRepository?.updateOnlineStatus(item.publicKey, isOnline)
addLog(" updateOnlineStatus called")
}
}
}
}

View File

@@ -639,30 +639,35 @@ fun ChatDetailScreen(
keyboardController?.hide()
focusManager.clearFocus()
showMenu = true
}
},
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "More",
tint = headerIconColor
tint = headerIconColor,
modifier = Modifier.size(26.dp)
)
}
// Выпадающее меню - чистый дизайн без артефактов
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier
.width(220.dp)
.clip(RoundedCornerShape(16.dp))
.background(
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
)
.shadow(
elevation = 8.dp,
shape = RoundedCornerShape(16.dp)
)
MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy(
surface = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
)
) {
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier
.width(220.dp)
.background(
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
shape = RoundedCornerShape(16.dp)
)
) {
// Delete Chat - красный
DropdownMenuItem(
text = {
@@ -689,7 +694,10 @@ fun ChatDetailScreen(
showMenu = false
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
}
},
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
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(
modifier = Modifier
.fillMaxSize()
.graphicsLayer { clip = false }
.padding(paddingValues)
) {
// Список сообщений - динамический padding для клавиатуры/эмодзи
Box(modifier = Modifier.fillMaxSize().padding(bottom = listBottomPadding)) {
// 🔥 graphicsLayer(clip = false) - позволяет пузырькам выходить за границы padding
Box(modifier = Modifier
.fillMaxSize()
.graphicsLayer { clip = false }
.padding(bottom = listBottomPadding)
) {
when {
// 🔥 СКЕЛЕТОН - показываем пока загружаются сообщения
isLoading -> {
@@ -1483,11 +1504,12 @@ private fun MessageBubble(
label = "selectionAlpha"
)
// 🔥 ОПТИМИЗАЦИЯ: Кешируем цвета - они не меняются для одного сообщения
// 🔥 Цвета - НАШИ ОРИГИНАЛЬНЫЕ
val bubbleColor = remember(message.isOutgoing, isDarkTheme) {
if (message.isOutgoing) {
PrimaryBlue
PrimaryBlue // Исходящие - наш синий
} else {
// Входящие - наши цвета
if (isDarkTheme) Color(0xFF212121) else Color(0xFFF5F5F5)
}
}
@@ -1495,18 +1517,20 @@ private fun MessageBubble(
if (message.isOutgoing) Color.White
else if (isDarkTheme) Color.White else Color(0xFF000000)
}
// Время - наши оригинальные цвета
val timeColor = remember(message.isOutgoing, isDarkTheme) {
if (message.isOutgoing) Color.White.copy(alpha = 0.7f)
else if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
}
// 🔥 ОПТИМИЗАЦИЯ: Кешируем форму bubble
// 🔥 TELEGRAM STYLE: Форма пузырька - более мягкие углы
val bubbleShape = remember(message.isOutgoing, showTail) {
RoundedCornerShape(
topStart = 18.dp,
topEnd = 18.dp,
bottomStart = if (message.isOutgoing) 18.dp else (if (showTail) 4.dp else 18.dp),
bottomEnd = if (message.isOutgoing) (if (showTail) 4.dp else 18.dp) else 18.dp
topStart = 16.dp,
topEnd = 16.dp,
// Хвостик: маленький радиус (4dp) только у нижнего угла со стороны отправителя
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(
modifier =
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) }
.graphicsLayer {
// ❌ УБРАЛИ: alpha = alpha * selectionAlpha и translationY
// Оставляем только selection анимацию
this.alpha = selectionAlpha
this.scaleX = selectionScale
this.scaleY = selectionScale
@@ -1607,7 +1630,7 @@ private fun MessageBubble(
Box(
modifier =
Modifier.widthIn(max = 300.dp)
Modifier.widthIn(max = 280.dp) // 🔥 TELEGRAM: чуть уже (280dp)
.combinedClickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
@@ -1616,9 +1639,10 @@ private fun MessageBubble(
)
.clip(bubbleShape)
.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 {
// Reply bubble (цитата)
message.replyData?.let { reply ->
@@ -1627,32 +1651,34 @@ private fun MessageBubble(
isOutgoing = message.isOutgoing,
isDarkTheme = isDarkTheme
)
Spacer(modifier = Modifier.height(6.dp))
Spacer(modifier = Modifier.height(4.dp)) // Меньше отступ
}
// Текст и время в одной строке (Row)
Row(
verticalAlignment = Alignment.Bottom, // Выравнивание по нижней границе (baseline)
horizontalArrangement = Arrangement.spacedBy(6.dp)
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(8.dp) // Чуть больше отступ до времени
) {
// Текст (не растягивается на всю ширину)
// 🔥 TELEGRAM: Текст 17sp, lineHeight 22sp, letterSpacing -0.4sp
AppleEmojiText(
text = message.text,
color = textColor,
fontSize = 16.sp,
fontSize = 17.sp,
modifier = Modifier.weight(1f, fill = false)
)
// Время и статус справа
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.padding(bottom = 1.dp) // Небольшая коррекция для выравнивания
horizontalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.padding(bottom = 2.dp)
) {
// 🔥 TELEGRAM: Время 11sp, italic style
Text(
text = timeFormat.format(message.timestamp),
color = timeColor,
fontSize = 11.sp
fontSize = 11.sp,
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic
)
if (message.isOutgoing) {
AnimatedMessageStatus(
@@ -1781,7 +1807,7 @@ private fun AnimatedMessageStatus(
}
/**
* 🔥 Reply bubble (цитата) внутри сообщения - как в React Native
* 🔥 Reply bubble (цитата) внутри сообщения
* Стиль: вертикальная линия слева + имя + текст
*/
@Composable
@@ -1790,7 +1816,7 @@ private fun ReplyBubble(
isOutgoing: Boolean,
isDarkTheme: Boolean
) {
// Цвета как в React Native
// НАШИ ОРИГИНАЛЬНЫЕ ЦВЕТА
val backgroundColor = if (isOutgoing) {
Color.Black.copy(alpha = 0.15f)
} else {
@@ -1809,7 +1835,7 @@ private fun ReplyBubble(
PrimaryBlue
}
val textColor = if (isOutgoing) {
val replyTextColor = if (isOutgoing) {
Color.White.copy(alpha = 0.85f)
} else {
if (isDarkTheme) Color.White else Color.Black
@@ -1817,12 +1843,12 @@ private fun ReplyBubble(
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth()
.height(IntrinsicSize.Min)
.clip(RoundedCornerShape(4.dp))
.background(backgroundColor)
) {
// Вертикальная линия слева (как borderLeft в React Native)
// 🔥 TELEGRAM: Вертикальная линия слева 3dp
Box(
modifier = Modifier
.width(3.dp)
@@ -1833,22 +1859,24 @@ private fun ReplyBubble(
// Контент
Column(
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 = replyData.senderName,
color = nameColor,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Текст цитируемого сообщения
// 🔥 TELEGRAM: Текст цитаты 14sp, Regular
Text(
text = replyData.text.ifEmpty { "..." },
color = textColor,
color = replyTextColor,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -2007,6 +2035,7 @@ private fun MessageInputBar(
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer { clip = false }
.windowInsetsPadding(WindowInsets.ime.only(WindowInsetsSides.Bottom))
) {
// Если пользователь заблокирован - показываем BlockedChatFooter (плоский как инпут)
@@ -2053,6 +2082,7 @@ private fun MessageInputBar(
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer { clip = false }
) {
// Верхний border (как в архиве)
Box(
@@ -2240,8 +2270,8 @@ private fun MessageInputBar(
// Мгновенно когда клавиатура открывается
snap()
} else {
// Быстрая анимация для мгновенного отклика (как в Telegram)
tween(durationMillis = 150, easing = TelegramEasing)
// Быстрая анимация как обычная клавиатура (200ms)
tween(durationMillis = 200, easing = FastOutSlowInEasing)
},
label = "EmojiPanelHeight"
)
@@ -2250,10 +2280,9 @@ private fun MessageInputBar(
modifier = Modifier
.fillMaxWidth()
.height(animatedHeight)
.clipToBounds()
) {
// 🚀 Рендерим панель только когда нужно
if (showEmojiPicker && !isKeyboardVisible && animatedHeight > 0.dp) {
if (showEmojiPicker && !isKeyboardVisible) {
AppleEmojiPickerPanel(
isDarkTheme = isDarkTheme,
onEmojiSelected = { emoji ->

View File

@@ -36,6 +36,7 @@ import coil.request.ImageRequest
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
@@ -505,14 +506,8 @@ fun EmojiButton(
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.85f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessHigh
),
label = "emojiScale"
)
// 🚀 Убираем анимацию scale для производительности
val scale = if (isPressed) 0.85f else 1f
val imageRequest = remember(unified) {
ImageRequest.Builder(context)
@@ -613,11 +608,15 @@ fun AppleEmojiPickerPanel(
var selectedCategory by remember { mutableStateOf(EMOJI_CATEGORIES[0]) }
val gridState = rememberLazyGridState()
// Загружаем эмодзи если еще не загружены (синхронно из кеша если уже загружено)
LaunchedEffect(Unit) {
// 🚀 Предзагружаем эмодзи СИНХРОННО при создании компонента
val emojisReady = remember {
if (!EmojiCache.isLoaded) {
EmojiCache.loadEmojis(context)
// Загружаем синхронно для мгновенного отображения
runBlocking {
EmojiCache.loadEmojis(context)
}
}
true
}
// Текущие эмодзи для выбранной категории - используем derivedStateOf для оптимизации
@@ -631,7 +630,7 @@ fun AppleEmojiPickerPanel(
}
}
// Сбрасываем скролл при смене категории
// 🚀 Убираем анимацию скролла для мгновенного переключения категорий
LaunchedEffect(selectedCategory) {
gridState.scrollToItem(0)
}
@@ -700,6 +699,7 @@ fun AppleEmojiPickerPanel(
)
}
} else {
// 🚀 Оптимизированная LazyVerticalGrid для быстрого рендеринга
LazyVerticalGrid(
state = gridState,
columns = GridCells.Fixed(8),