android: micro-polish chat bubbles and composer visuals
Some checks failed
Android CI / android (push) Has been cancelled
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
Codex
2026-03-10 01:48:07 +03:00
parent 7781cf83e4
commit 55af1f78b6
2 changed files with 62 additions and 26 deletions

View File

@@ -901,3 +901,13 @@
- Added top mini audio strip under pinned area: - Added top mini audio strip under pinned area:
- shows latest audio/voice context from loaded chat, - shows latest audio/voice context from loaded chat,
- includes play affordance, speed badge, and dismiss action. - includes play affordance, speed badge, and dismiss action.
### Step 126 - Message bubble/composer micro-polish
- Updated message bubble sizing and density:
- reduced bubble width for cleaner conversation rhythm,
- tighter vertical spacing,
- text style adjusted for better readability.
- Refined bottom composer visuals:
- switched to Telegram-like rounded input container look,
- emoji/attach/send buttons now use circular tinted surfaces,
- text input moved to filled style with hidden indicator lines.

View File

@@ -58,6 +58,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@@ -1012,9 +1014,9 @@ fun ChatScreen(
.fillMaxWidth() .fillMaxWidth()
.navigationBarsPadding() .navigationBarsPadding()
.imePadding() .imePadding()
.padding(horizontal = 10.dp, vertical = 8.dp), .padding(horizontal = 10.dp, vertical = 6.dp),
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.92f), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.95f),
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(22.dp),
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -1023,30 +1025,49 @@ fun ChatScreen(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp), horizontalArrangement = Arrangement.spacedBy(6.dp),
) { ) {
IconButton( Surface(
onClick = { /* emoji picker step */ }, shape = CircleShape,
enabled = state.canSendMessages, color = MaterialTheme.colorScheme.primary.copy(alpha = 0.14f),
) { ) {
Icon( IconButton(
imageVector = Icons.Filled.EmojiEmotions, onClick = { /* emoji picker step */ },
contentDescription = "Emoji", enabled = state.canSendMessages,
) ) {
Icon(
imageVector = Icons.Filled.EmojiEmotions,
contentDescription = "Emoji",
)
}
} }
OutlinedTextField( TextField(
value = state.inputText, value = state.inputText,
onValueChange = onInputChanged, onValueChange = onInputChanged,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
placeholder = { Text("Message") }, placeholder = { Text("Message") },
shape = RoundedCornerShape(14.dp),
maxLines = 4, maxLines = 4,
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.25f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
),
) )
IconButton( Surface(
onClick = onPickMedia, shape = CircleShape,
enabled = state.canSendMessages && !state.isUploadingMedia && !state.isRecordingVoice, color = MaterialTheme.colorScheme.primary.copy(alpha = 0.14f),
) { ) {
if (state.isUploadingMedia) { IconButton(
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp) onClick = onPickMedia,
} else { enabled = state.canSendMessages && !state.isUploadingMedia && !state.isRecordingVoice,
Icon(imageVector = Icons.Filled.AttachFile, contentDescription = "Attach") ) {
if (state.isUploadingMedia) {
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
} else {
Icon(imageVector = Icons.Filled.AttachFile, contentDescription = "Attach")
}
} }
} }
val canSend = state.canSendMessages && val canSend = state.canSendMessages &&
@@ -1054,11 +1075,16 @@ fun ChatScreen(
!state.isUploadingMedia && !state.isUploadingMedia &&
state.inputText.isNotBlank() state.inputText.isNotBlank()
if (canSend) { if (canSend) {
IconButton( Surface(
onClick = onSendClick, shape = CircleShape,
enabled = state.canSendMessages && !state.isUploadingMedia, color = MaterialTheme.colorScheme.primary.copy(alpha = 0.18f),
) { ) {
Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send") IconButton(
onClick = onSendClick,
enabled = state.canSendMessages && !state.isUploadingMedia,
) {
Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send")
}
} }
} else { } else {
VoiceHoldToRecordButton( VoiceHoldToRecordButton(
@@ -1291,7 +1317,7 @@ private fun MessageBubble(
} }
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.84f) .fillMaxWidth(0.8f)
.widthIn(min = 82.dp) .widthIn(min = 82.dp)
.background( .background(
color = when { color = when {
@@ -1305,8 +1331,8 @@ private fun MessageBubble(
onClick = onClick, onClick = onClick,
onLongClick = onLongPress, onLongClick = onLongPress,
) )
.padding(horizontal = 10.dp, vertical = 8.dp), .padding(horizontal = 10.dp, vertical = 7.dp),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(3.dp),
) { ) {
if (!isOutgoing && !message.senderDisplayName.isNullOrBlank()) { if (!isOutgoing && !message.senderDisplayName.isNullOrBlank()) {
Text( Text(
@@ -1373,7 +1399,7 @@ private fun MessageBubble(
if (mainText != null || message.attachments.isEmpty()) { if (mainText != null || message.attachments.isEmpty()) {
Text( Text(
text = mainText ?: "[${message.type}]", text = mainText ?: "[${message.type}]",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyLarge,
color = textColor, color = textColor,
) )
} }