feat: Enhance send and mic button animations in MessageInputBar for improved user experience

This commit is contained in:
k1ngsterr1
2026-01-12 18:35:10 +05:00
parent 9addd41571
commit d117d869cd
3 changed files with 65 additions and 248 deletions

View File

@@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset 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 { Box {
IconButton( IconButton(
@@ -1627,79 +1611,16 @@ private fun MessageInputBar(
) )
} }
} else { } else {
// 🔥 REACT NATIVE STYLE: Attach | Glass Input | Mic // 🔥 TELEGRAM STYLE: простой фон, все кнопки внутри
Row(
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 }
)
}
// GLASS INPUT - расширяется, содержит Emoji + TextField + Send
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.heightIn(min = 48.dp, max = if (hasReply) 200.dp else 140.dp) .padding(horizontal = 0.dp, vertical = 0.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( .background(
brush = Brush.verticalGradient( color = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7)
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( AnimatedVisibility(
visible = hasReply, visible = hasReply,
enter = fadeIn(tween(150)) + expandVertically(), enter = fadeIn(tween(150)) + expandVertically(),
@@ -1708,19 +1629,17 @@ private fun MessageInputBar(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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 verticalAlignment = Alignment.CenterVertically
) { ) {
// Вертикальная синяя линия (как в React Native)
Box( Box(
modifier = Modifier modifier = Modifier
.width(3.dp) .width(3.dp)
.height(36.dp) .height(32.dp)
.background(PrimaryBlue, RoundedCornerShape(1.5.dp)) .background(PrimaryBlue, RoundedCornerShape(1.5.dp))
) )
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
// Контент reply
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = if (isForwardMode) "Forward message${if (replyMessages.size > 1) "s" else ""}" text = if (isForwardMode) "Forward message${if (replyMessages.size > 1) "s" else ""}"
@@ -1744,8 +1663,6 @@ private fun MessageInputBar(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
// Кнопка X
IconButton( IconButton(
onClick = onCloseReply, onClick = onCloseReply,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
@@ -1761,18 +1678,38 @@ private fun MessageInputBar(
} }
} }
// Input Row внутри Glass // INPUT ROW - как в Telegram
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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 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 // TEXT INPUT
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .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 contentAlignment = Alignment.CenterStart
) { ) {
AppleEmojiTextField( AppleEmojiTextField(
@@ -1781,68 +1718,25 @@ private fun MessageInputBar(
textColor = textColor, textColor = textColor,
textSize = 16f, textSize = 16f,
hint = "Message", hint = "Message",
hintColor = if (isDarkTheme) Color.White.copy(alpha = 0.35f) hintColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93),
else Color.Black.copy(alpha = 0.35f),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
// Right Zone: Emoji + Send (как в React Native) Spacer(modifier = Modifier.width(4.dp))
Box(
modifier = Modifier
.align(Alignment.Bottom)
.padding(bottom = 8.dp),
contentAlignment = Alignment.BottomEnd
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.Bottom
) {
// EMOJI BUTTON
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = { toggleEmojiPicker() }
),
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)
)
}
// SEND BUTTON - только когда canSend // ATTACH / MIC / SEND BUTTON (справа)
androidx.compose.animation.AnimatedVisibility( if (canSend) {
visible = canSend, // SEND BUTTON
enter = fadeIn(tween(200)) + scaleIn( IconButton(
initialScale = 0.5f, onClick = { handleSend() },
animationSpec = tween(220, easing = FastOutSlowInEasing) modifier = Modifier.size(48.dp)
),
exit = fadeOut(tween(200)) + scaleOut(
targetScale = 0.5f,
animationSpec = tween(220)
)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(52.dp) .size(34.dp)
.height(34.dp) .clip(CircleShape)
.clip(RoundedCornerShape(17.dp)) .background(PrimaryBlue),
.background(PrimaryBlue)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = { handleSend() }
),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
@@ -1853,52 +1747,22 @@ private fun MessageInputBar(
) )
} }
} }
} } else {
} // MIC BUTTON
} // End of Input Row IconButton(
} // End of Glass Column onClick = { /* TODO: Voice recording */ },
modifier = Modifier.size(48.dp)
// 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( Icon(
Icons.Default.Mic, Icons.Default.Mic,
contentDescription = "Voice", contentDescription = "Voice",
tint = if (isDarkTheme) Color.White else Color(0xFF333333), tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93),
modifier = Modifier.size(22.dp) modifier = Modifier.size(26.dp)
) )
} }
} }
} // End of outer Row }
}
} // End of else (not blocked) } // End of else (not blocked)
// Apple Emoji Picker - только показываем если не заблокирован // Apple Emoji Picker - только показываем если не заблокирован

View File

@@ -614,11 +614,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
val opponent = opponentKey ?: return val opponent = opponentKey ?: return
try { try {
ProtocolManager.addLog("💾 Saving dialog: ${lastMessage.take(20)}...")
val existingDialog = dialogDao.getDialog(account, opponent) val existingDialog = dialogDao.getDialog(account, opponent)
if (existingDialog != null) { if (existingDialog != null) {
// Обновляем последнее сообщение // Обновляем последнее сообщение
dialogDao.updateLastMessage(account, opponent, lastMessage, timestamp) dialogDao.updateLastMessage(account, opponent, lastMessage, timestamp)
ProtocolManager.addLog("✅ Dialog updated (existing)")
} else { } else {
// Создаём новый диалог // Создаём новый диалог
dialogDao.insertDialog(DialogEntity( dialogDao.insertDialog(DialogEntity(
@@ -629,8 +631,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
lastMessage = lastMessage, lastMessage = lastMessage,
lastMessageTimestamp = timestamp lastMessageTimestamp = timestamp
)) ))
ProtocolManager.addLog("✅ Dialog created (new)")
} }
ProtocolManager.addLog("💾 Dialog saved")
} catch (e: Exception) { } catch (e: Exception) {
ProtocolManager.addLog("❌ Dialog save error: ${e.message}") ProtocolManager.addLog("❌ Dialog save error: ${e.message}")
Log.e(TAG, "Dialog save error", e) Log.e(TAG, "Dialog save error", e)

View File

@@ -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 // Status dialog state
var showStatusDialog by remember { mutableStateOf(false) } var showStatusDialog by remember { mutableStateOf(false) }
@@ -418,19 +413,6 @@ fun ChatsListScreen(
}, },
title = { title = {
Row( 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 verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
@@ -528,38 +510,7 @@ fun ChatsListScreen(
} }
} }
// Console button - always visible at bottom left // Console button removed
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)
)
}
}
} }
} }
} // Close ModalNavigationDrawer } // Close ModalNavigationDrawer