fix(ui): resolve double-resizing overlap bug with dynamic keyboard insets padding

This commit is contained in:
Bruno Deanoz 2026-06-18 16:33:21 +02:00
parent 43ca73c04a
commit cbcbd4f76b
2 changed files with 23 additions and 8 deletions

View file

@ -21,10 +21,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -128,6 +131,13 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
val dateLine = remember { val dateLine = remember {
now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.GERMAN)) now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.GERMAN))
} }
// Query soft-keyboard height dynamically to dynamically expand the bottom spacer.
// This allows suggestion pills to push upwards cleanly above the keyboard & floating input,
// completely preventing any overlaps without squeezing the greeting, date, or Kaizen orb.
val imeBottom = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
val bottomSpacerHeight = remember(imeBottom) { 142.dp + imeBottom }
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -145,7 +155,7 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
Text(dateLine, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 13.sp) Text(dateLine, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 13.sp)
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
SuggestionPills(onPick) SuggestionPills(onPick)
Spacer(Modifier.height(142.dp)) // Clear the floating bottom dock Spacer(Modifier.height(bottomSpacerHeight)) // Dynamically expands to push layout out of the floating bottom dock
} }
} }

View file

@ -4,12 +4,15 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@ -80,23 +83,25 @@ fun ChatScreen(userName: String = "Bruno") {
MeshBackground { MeshBackground {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
val imeBottom = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
// 1. Scrollable Chat Content // 1. Scrollable Chat Content
// Stretches to the full screen, allowing bubbles to scroll cleanly behind the floating glass panels. // Stretches to the full screen, allowing bubbles to scroll cleanly behind the floating glass panels.
// No root imePadding() here to prevent double-resizing/pushing bugs on standard Android windows.
if (messages.isEmpty()) { if (messages.isEmpty()) {
EmptyHero( EmptyHero(
userName = userName, userName = userName,
onPick = { haptics.tick(); send(it) }, onPick = { haptics.tick(); send(it) },
modifier = Modifier.imePadding() // Viewport shrinks so the scrollable Column adapts without warping modifier = Modifier.fillMaxSize()
) )
} else { } else {
val bottomPadding = remember(imeBottom) { 152.dp + imeBottom }
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.imePadding(), // Adjusts viewport height so scrollable content stops exactly above the keyboard
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = 92.dp, // Leaves luxurious room for the floating KaizenHeader + status bars top = 92.dp, // Leaves luxurious room for the floating KaizenHeader + status bars
bottom = 152.dp, // Leaves luxurious room for the floating ModePills + ChatInput + bottom margins bottom = bottomPadding, // Dynamically expands as the keyboard rises to keep messages clear of the floating dock
start = 16.dp, start = 16.dp,
end = 16.dp end = 16.dp
), ),
@ -118,7 +123,7 @@ fun ChatScreen(userName: String = "Bruno") {
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.navigationBarsPadding() .navigationBarsPadding()
.imePadding() .imePadding() // Only the bottom controls float up, perfectly placing them above the keyboard
) { ) {
ModePillsRow(selected = selectedMode, onSelect = { selectedMode = it; haptics.tick() }) ModePillsRow(selected = selectedMode, onSelect = { selectedMode = it; haptics.tick() })
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))