feat(ui): implement tablet optimizations and gemini-style dynamic keyboard suggestion-hiding

This commit is contained in:
Bruno Deanoz 2026-06-18 16:40:18 +02:00
parent e13f6798db
commit e3ea6d241f
2 changed files with 34 additions and 12 deletions

View file

@ -21,10 +21,14 @@ 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.isImeVisible
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
@ -129,6 +133,11 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.GERMAN)) now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.GERMAN))
} }
// Dynamic keyboard visibility check (Google Gemini style)
// When the soft keyboard is visible, we completely hide the Suggestion Pills
// to give the user maximum typing space and keep the layout extremely clean.
val isKeyboardOpen = WindowInsets.isImeVisible
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -144,8 +153,12 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
Text("Was liegt an?", color = cs.onBackground, fontSize = 32.sp, fontWeight = FontWeight.Bold) Text("Was liegt an?", color = cs.onBackground, fontSize = 32.sp, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
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))
SuggestionPills(onPick) if (!isKeyboardOpen) {
Spacer(Modifier.height(32.dp))
SuggestionPills(onPick)
}
Spacer(Modifier.height(142.dp)) // Clear the floating bottom dock Spacer(Modifier.height(142.dp)) // Clear the floating bottom dock
} }
} }

View file

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -23,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.kaizen.app.haptics.rememberHaptics import dev.kaizen.app.haptics.rememberHaptics
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -39,6 +41,10 @@ fun ChatScreen(userName: String = "Bruno") {
var nextId by remember { mutableStateOf(0L) } var nextId by remember { mutableStateOf(0L) }
var selectedMode by remember { mutableStateOf("Standard") } var selectedMode by remember { mutableStateOf("Standard") }
// Tablet & Landscape optimization: Detect large screens and cap the content width
val configuration = LocalConfiguration.current
val isTablet = configuration.screenWidthDp > 600
fun send(text: String) { fun send(text: String) {
val trimmed = text.trim() val trimmed = text.trim()
if (trimmed.isEmpty() || isStreaming) return if (trimmed.isEmpty() || isStreaming) return
@ -78,21 +84,24 @@ fun ChatScreen(userName: String = "Bruno") {
} }
MeshBackground { MeshBackground {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
// 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.
// Since the OS physically resizes the window when the keyboard opens, the Box container shrinks natively, // Responsive width capping and centering on tablets.
// placing the bottom dock exactly on top of the keyboard. We use constant Paddings to clear the docks.
if (messages.isEmpty()) { if (messages.isEmpty()) {
EmptyHero( EmptyHero(
userName = userName, userName = userName,
onPick = { haptics.tick(); send(it) }, onPick = { haptics.tick(); send(it) },
modifier = Modifier.fillMaxSize() modifier = Modifier
.fillMaxSize()
.then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier)
) )
} else { } else {
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier),
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 = 152.dp, // Leaves luxurious room for the floating ModePills + ChatInput + bottom margins
@ -105,20 +114,20 @@ fun ChatScreen(userName: String = "Bruno") {
} }
} }
// 2. Floating Header (Translucent Frosted Glass) // 2. Floating Header (Translucent Frosted Glass, responsive centering)
KaizenHeader( KaizenHeader(
modifier = Modifier modifier = Modifier
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.statusBarsPadding() .statusBarsPadding()
.then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier)
) )
// 3. Floating Bottom Control Dock // 3. Floating Bottom Control Dock (responsive centering and clear bottom margins)
// Positioned at the bottom of the window. When the keyboard is active, the OS resizes the window
// and this dock naturally sits exactly on top of the keyboard without any double-padding.
Column( Column(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.navigationBarsPadding() .navigationBarsPadding()
.then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier)
) { ) {
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))
@ -128,7 +137,7 @@ fun ChatScreen(userName: String = "Bruno") {
onSend = { send(input) }, onSend = { send(input) },
enabled = !isStreaming, enabled = !isStreaming,
) )
Spacer(Modifier.height(12.dp)) // Extra breathing margin at the bottom of the floating card Spacer(Modifier.height(16.dp)) // Increased from 12.dp to 16.dp to completely clear keyboard edges on all screens!
} }
} }
} }