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.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 - только показываем если не заблокирован

View File

@@ -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)

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
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