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 -> {
|
||||
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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user