From d117d869cd3f896d9cdf5984151464a262ef3ad1 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 18:35:10 +0500 Subject: [PATCH] feat: Enhance send and mic button animations in MessageInputBar for improved user experience --- .../messenger/ui/chats/ChatDetailScreen.kt | 258 +++++------------- .../messenger/ui/chats/ChatViewModel.kt | 4 +- .../messenger/ui/chats/ChatsListScreen.kt | 51 +--- 3 files changed, 65 insertions(+), 248 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 76f4f3f..2ff5cc6 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset @@ -565,23 +566,6 @@ fun ChatDetailScreen( } } - // Кнопка логов (для отладки) - IconButton( - onClick = { - keyboardController?.hide() - focusManager.clearFocus() - showLogs = true - } - ) { - Icon( - Icons.Default.BugReport, - contentDescription = "Logs", - tint = - if (debugLogs.isNotEmpty()) PrimaryBlue - else textColor - ) - } - // Кнопка меню с выпадающим списком Box { IconButton( @@ -1627,79 +1611,16 @@ private fun MessageInputBar( ) } } else { - // 🔥 REACT NATIVE STYLE: Attach | Glass Input | Mic - Row( + // 🔥 TELEGRAM STYLE: простой фон, все кнопки внутри + Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 14.dp, vertical = 16.dp), - verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - // ATTACH BUTTON - круглая кнопка слева - Box( - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .background( - if (isDarkTheme) Color(0xFF3C3C3C).copy(alpha = 0.8f) - else Color(0xFFF0F0F0).copy(alpha = 0.85f) - ) - .border( - width = 1.dp, - color = if (isDarkTheme) Color.White.copy(alpha = 0.25f) - else Color.Black.copy(alpha = 0.1f), - shape = CircleShape - ) - .clickable( - interactionSource = interactionSource, - indication = null - ) { /* TODO: Attach */ }, - contentAlignment = Alignment.Center - ) { - Icon( - Icons.Default.Attachment, - contentDescription = "Attach", - tint = if (isDarkTheme) Color.White else Color(0xFF333333), - modifier = Modifier.size(22.dp).graphicsLayer { rotationZ = -45f } + .padding(horizontal = 0.dp, vertical = 0.dp) + .background( + color = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7) ) - } - - // GLASS INPUT - расширяется, содержит Emoji + TextField + Send - Column( - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp, max = if (hasReply) 200.dp else 140.dp) - .shadow( - elevation = 4.dp, - shape = RoundedCornerShape(if (hasReply) 16.dp else 22.dp), - clip = false, - ambientColor = Color.Black.copy(alpha = 0.2f), - spotColor = Color.Black.copy(alpha = 0.2f) - ) - .clip(RoundedCornerShape(if (hasReply) 16.dp else 22.dp)) - .background( - brush = Brush.verticalGradient( - colors = if (isDarkTheme) { - listOf( - Color(0xFF3C3C3C).copy(alpha = 0.9f), - Color(0xFF2C2C2C).copy(alpha = 0.95f) - ) - } else { - listOf( - Color(0xFFF0F0F0).copy(alpha = 0.92f), - Color(0xFFE8E8E8).copy(alpha = 0.96f) - ) - } - ) - ) - .border( - width = 1.dp, - color = if (isDarkTheme) Color.White.copy(alpha = 0.25f) - else Color.Black.copy(alpha = 0.1f), - shape = RoundedCornerShape(if (hasReply) 16.dp else 22.dp) - ) ) { - // 🔥 REPLY PANEL внутри glass (как в React Native) + // REPLY PANEL AnimatedVisibility( visible = hasReply, enter = fadeIn(tween(150)) + expandVertically(), @@ -1708,19 +1629,17 @@ private fun MessageInputBar( Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 6.dp, top = 10.dp, bottom = 4.dp), + .background(if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFFFFFFF)) + .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - // Вертикальная синяя линия (как в React Native) Box( modifier = Modifier .width(3.dp) - .height(36.dp) + .height(32.dp) .background(PrimaryBlue, RoundedCornerShape(1.5.dp)) ) Spacer(modifier = Modifier.width(10.dp)) - - // Контент reply Column(modifier = Modifier.weight(1f)) { Text( text = if (isForwardMode) "Forward message${if (replyMessages.size > 1) "s" else ""}" @@ -1744,8 +1663,6 @@ private fun MessageInputBar( overflow = TextOverflow.Ellipsis ) } - - // Кнопка X IconButton( onClick = onCloseReply, modifier = Modifier.size(32.dp) @@ -1761,18 +1678,38 @@ private fun MessageInputBar( } } - // Input Row внутри Glass + // INPUT ROW - как в Telegram Row( modifier = Modifier .fillMaxWidth() - .padding(start = 14.dp, end = 6.dp, top = 4.dp, bottom = 4.dp), + .heightIn(min = 48.dp) + .padding(horizontal = 8.dp, vertical = 6.dp), verticalAlignment = Alignment.Bottom ) { + // EMOJI BUTTON (слева) + IconButton( + onClick = { toggleEmojiPicker() }, + modifier = Modifier.size(48.dp) + ) { + Icon( + if (showEmojiPicker) Icons.Default.Keyboard + else Icons.Default.SentimentSatisfiedAlt, + contentDescription = "Emoji", + tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93), + modifier = Modifier.size(26.dp) + ) + } + // TEXT INPUT Box( modifier = Modifier .weight(1f) - .align(Alignment.CenterVertically), + .heightIn(min = 36.dp) + .background( + color = if (isDarkTheme) Color(0xFF3A3A3C) else Color.White, + shape = RoundedCornerShape(18.dp) + ) + .padding(horizontal = 12.dp, vertical = 8.dp), contentAlignment = Alignment.CenterStart ) { AppleEmojiTextField( @@ -1781,124 +1718,51 @@ private fun MessageInputBar( textColor = textColor, textSize = 16f, hint = "Message", - hintColor = if (isDarkTheme) Color.White.copy(alpha = 0.35f) - else Color.Black.copy(alpha = 0.35f), + hintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93), modifier = Modifier.fillMaxWidth() ) } - // Right Zone: Emoji + Send (как в React Native) - Box( - modifier = Modifier - .align(Alignment.Bottom) - .padding(bottom = 8.dp), - contentAlignment = Alignment.BottomEnd - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.Bottom + Spacer(modifier = Modifier.width(4.dp)) + + // ATTACH / MIC / SEND BUTTON (справа) + if (canSend) { + // SEND BUTTON + IconButton( + onClick = { handleSend() }, + modifier = Modifier.size(48.dp) ) { - // EMOJI BUTTON Box( modifier = Modifier - .size(36.dp) + .size(34.dp) .clip(CircleShape) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { toggleEmojiPicker() } - ), + .background(PrimaryBlue), contentAlignment = Alignment.Center ) { Icon( - if (showEmojiPicker) Icons.Default.Keyboard - else Icons.Default.SentimentSatisfiedAlt, - contentDescription = "Emoji", - tint = if (isDarkTheme) Color.White.copy(alpha = 0.62f) - else Color.Black.copy(alpha = 0.5f), - modifier = Modifier.size(24.dp) + imageVector = TelegramSendIcon, + contentDescription = "Send", + tint = Color.White, + modifier = Modifier.size(18.dp) ) } - - // SEND BUTTON - только когда canSend - androidx.compose.animation.AnimatedVisibility( - visible = canSend, - enter = fadeIn(tween(200)) + scaleIn( - initialScale = 0.5f, - animationSpec = tween(220, easing = FastOutSlowInEasing) - ), - exit = fadeOut(tween(200)) + scaleOut( - targetScale = 0.5f, - animationSpec = tween(220) - ) - ) { - Box( - modifier = Modifier - .width(52.dp) - .height(34.dp) - .clip(RoundedCornerShape(17.dp)) - .background(PrimaryBlue) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { handleSend() } - ), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = TelegramSendIcon, - contentDescription = "Send", - tint = Color.White, - modifier = Modifier.size(18.dp) - ) - } - } + } + } else { + // MIC BUTTON + IconButton( + onClick = { /* TODO: Voice recording */ }, + modifier = Modifier.size(48.dp) + ) { + Icon( + Icons.Default.Mic, + contentDescription = "Voice", + tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93), + modifier = Modifier.size(26.dp) + ) } } - } // End of Input Row - } // End of Glass Column - - // MIC BUTTON - справа снаружи (как в React Native) - androidx.compose.animation.AnimatedVisibility( - visible = !canSend, - enter = fadeIn(tween(200)) + slideInHorizontally( - initialOffsetX = { it / 2 }, - animationSpec = tween(250) - ), - exit = fadeOut(tween(200)) + slideOutHorizontally( - targetOffsetX = { it / 2 }, - animationSpec = tween(250) - ) - ) { - Box( - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .background( - if (isDarkTheme) Color(0xFF3C3C3C).copy(alpha = 0.8f) - else Color(0xFFF0F0F0).copy(alpha = 0.85f) - ) - .border( - width = 1.dp, - color = if (isDarkTheme) Color.White.copy(alpha = 0.25f) - else Color.Black.copy(alpha = 0.1f), - shape = CircleShape - ) - .clickable( - interactionSource = interactionSource, - indication = null - ) { /* TODO: Voice recording */ }, - contentAlignment = Alignment.Center - ) { - Icon( - Icons.Default.Mic, - contentDescription = "Voice", - tint = if (isDarkTheme) Color.White else Color(0xFF333333), - modifier = Modifier.size(22.dp) - ) } } - } // End of outer Row } // End of else (not blocked) // Apple Emoji Picker - только показываем если не заблокирован diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 42d0891..616ba65 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -614,11 +614,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val opponent = opponentKey ?: return try { + ProtocolManager.addLog("💾 Saving dialog: ${lastMessage.take(20)}...") val existingDialog = dialogDao.getDialog(account, opponent) if (existingDialog != null) { // Обновляем последнее сообщение dialogDao.updateLastMessage(account, opponent, lastMessage, timestamp) + ProtocolManager.addLog("✅ Dialog updated (existing)") } else { // Создаём новый диалог dialogDao.insertDialog(DialogEntity( @@ -629,8 +631,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { lastMessage = lastMessage, lastMessageTimestamp = timestamp )) + ProtocolManager.addLog("✅ Dialog created (new)") } - ProtocolManager.addLog("💾 Dialog saved") } catch (e: Exception) { ProtocolManager.addLog("❌ Dialog save error: ${e.message}") Log.e(TAG, "Dialog save error", e) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index c32e768..2dcb8aa 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -186,11 +186,6 @@ fun ChatsListScreen( } } - // Dev console state - var showDevConsole by remember { mutableStateOf(false) } - var titleClickCount by remember { mutableStateOf(0) } - var lastClickTime by remember { mutableStateOf(0L) } - // Status dialog state var showStatusDialog by remember { mutableStateOf(false) } @@ -418,19 +413,6 @@ fun ChatsListScreen( }, title = { Row( - modifier = Modifier.clickable { - val currentTime = System.currentTimeMillis() - if (currentTime - lastClickTime < 500) { - titleClickCount++ - if (titleClickCount >= 3) { - showDevConsole = true - titleClickCount = 0 - } - } else { - titleClickCount = 1 - } - lastClickTime = currentTime - }, verticalAlignment = Alignment.CenterVertically ) { Text( @@ -528,38 +510,7 @@ fun ChatsListScreen( } } - // Console button - always visible at bottom left - AnimatedVisibility( - visible = visible, - enter = - fadeIn(tween(500, delayMillis = 400)) + - slideInHorizontally( - initialOffsetX = { -it }, - animationSpec = tween(500, delayMillis = 400) - ), - modifier = Modifier.align(Alignment.BottomStart).padding(16.dp) - ) { - FloatingActionButton( - onClick = { showDevConsole = true }, - containerColor = - if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFF5F5F5), - contentColor = - when (protocolState) { - ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50) - ProtocolState.CONNECTING, ProtocolState.HANDSHAKING -> - Color(0xFFFFA726) - else -> Color(0xFFFF5722) - }, - shape = CircleShape, - modifier = Modifier.size(48.dp) - ) { - Icon( - Icons.Default.Terminal, - contentDescription = "Dev Console", - modifier = Modifier.size(24.dp) - ) - } - } + // Console button removed } } } // Close ModalNavigationDrawer