feat: Enhance send and mic button animations in MessageInputBar for improved user experience
This commit is contained in:
@@ -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 - только показываем если не заблокирован
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user