From 2d57afff61cca2f6fb181f5055bbbfecbf094ba0 Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Mon, 22 Jun 2026 00:31:33 +0200 Subject: [PATCH] input field: more breathing room, mic/send toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduced outer padding (12dp→8dp) for wider feel, increased inner padding (8dp→10dp) and min height (36dp→42dp) for more air. Placeholder text slightly smaller (15sp) and more subtle (0.6α). Text line-height increased to 24sp. Single action button: shows Mic icon when input is empty, morphs to Send button when text is entered — like WhatsApp/iMessage. Removed separate Call button that took space without function. Aligned action buttons to bottom for multi-line input. --- .../dev/kaizen/app/chat/ChatComponents.kt | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt index 9101046..465647b 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt @@ -89,6 +89,7 @@ import androidx.compose.material.icons.rounded.VideoFile import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.material.icons.rounded.Bolt +import androidx.compose.material.icons.rounded.Mic import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.Psychology import androidx.compose.material.icons.rounded.Tune @@ -463,10 +464,13 @@ fun ChatInput( val cs = MaterialTheme.colorScheme val isDark = isSystemInDarkTheme() + val hasContent = value.isNotBlank() || pendingFiles.any { it.uploaded != null } + val canSend = enabled && hasContent && pendingFiles.none { it.uploading } + GlassSurface( modifier = modifier .fillMaxWidth() - .padding(horizontal = 12.dp), + .padding(horizontal = 8.dp), shape = KaizenShapes.xl, tintAlpha = GlassTiers.input(isDark), shadowStack = KaizenShadows.level2, @@ -475,42 +479,38 @@ fun ChatInput( Column { if (pendingFiles.isNotEmpty()) { Row( - Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(start = 12.dp, end = 12.dp, top = 8.dp), + Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(start = 14.dp, end = 14.dp, top = 10.dp), horizontalArrangement = Arrangement.spacedBy(6.dp), ) { pendingFiles.forEach { pf -> PendingFileChip(pf, onRemove = { onRemoveFile(pf.id) }) } } } - Row(Modifier.padding(horizontal = 8.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) { - Box(Modifier.size(40.dp).clip(KaizenShapes.circle).clickable(onClick = onAddClick), contentAlignment = Alignment.Center) { + Row(Modifier.padding(start = 6.dp, end = 8.dp, top = 10.dp, bottom = 10.dp), verticalAlignment = Alignment.Bottom) { + Box(Modifier.size(42.dp).clip(KaizenShapes.circle).clickable(onClick = onAddClick), contentAlignment = Alignment.Center) { Icon(Icons.Rounded.Add, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp)) } - Box(Modifier.weight(1f).heightIn(min = 36.dp).padding(horizontal = 4.dp), contentAlignment = Alignment.CenterStart) { - if (value.isEmpty()) Text(stringResource(R.string.chat_input_placeholder), color = cs.onSurfaceVariant, fontSize = 16.sp) + Box(Modifier.weight(1f).heightIn(min = 42.dp).padding(horizontal = 6.dp), contentAlignment = Alignment.CenterStart) { + if (value.isEmpty()) Text(stringResource(R.string.chat_input_placeholder), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 15.sp) val accent = LocalKaizenAccent.current BasicTextField( value = value, onValueChange = onValueChange, - textStyle = TextStyle(color = cs.onBackground, fontSize = 16.sp, lineHeight = 22.sp), + textStyle = TextStyle(color = cs.onBackground, fontSize = 16.sp, lineHeight = 24.sp), cursorBrush = SolidColor(accent.primary), maxLines = 6, modifier = Modifier.fillMaxWidth(), ) } - GhostIconButton(Icons.Rounded.Call, stringResource(R.string.chat_call)) Spacer(Modifier.width(4.dp)) - val canSend = enabled && (value.isNotBlank() || pendingFiles.any { it.uploaded != null }) && pendingFiles.none { it.uploading } - SendButton(enabled = canSend, onClick = onSend) + if (hasContent) { + SendButton(enabled = canSend, onClick = onSend) + } else { + Box(Modifier.size(44.dp).clip(KaizenShapes.circle), contentAlignment = Alignment.Center) { + Icon(Icons.Rounded.Mic, stringResource(R.string.chat_call), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp)) + } + } } } } } -@Composable -private fun GhostIconButton(icon: ImageVector, contentDescription: String) { - val cs = MaterialTheme.colorScheme - Box(Modifier.size(40.dp).clip(KaizenShapes.circle), contentAlignment = Alignment.Center) { - Icon(icon, contentDescription, tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp)) - } -} - @Composable private fun PendingFileChip(pf: PendingFile, onRemove: () -> Unit) { val cs = MaterialTheme.colorScheme