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 8c0389e..8edaaaa 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt @@ -21,10 +21,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row 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.fillMaxWidth import androidx.compose.foundation.layout.height 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.size 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)) } + // 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( modifier = modifier .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) Spacer(Modifier.height(8.dp)) 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 } } diff --git a/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt b/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt index 9377990..a97c846 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -23,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp import dev.kaizen.app.haptics.rememberHaptics import kotlinx.coroutines.delay @@ -39,6 +41,10 @@ fun ChatScreen(userName: String = "Bruno") { var nextId by remember { mutableStateOf(0L) } 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) { val trimmed = text.trim() if (trimmed.isEmpty() || isStreaming) return @@ -78,21 +84,24 @@ fun ChatScreen(userName: String = "Bruno") { } MeshBackground { - Box(Modifier.fillMaxSize()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { // 1. Scrollable Chat Content // 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, - // placing the bottom dock exactly on top of the keyboard. We use constant Paddings to clear the docks. + // Responsive width capping and centering on tablets. if (messages.isEmpty()) { EmptyHero( userName = userName, onPick = { haptics.tick(); send(it) }, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier) ) } else { LazyColumn( state = listState, - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier), contentPadding = PaddingValues( 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 @@ -105,20 +114,20 @@ fun ChatScreen(userName: String = "Bruno") { } } - // 2. Floating Header (Translucent Frosted Glass) + // 2. Floating Header (Translucent Frosted Glass, responsive centering) KaizenHeader( modifier = Modifier .align(Alignment.TopCenter) .statusBarsPadding() + .then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier) ) - // 3. Floating Bottom Control Dock - // 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. + // 3. Floating Bottom Control Dock (responsive centering and clear bottom margins) Column( modifier = Modifier .align(Alignment.BottomCenter) .navigationBarsPadding() + .then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier) ) { ModePillsRow(selected = selectedMode, onSelect = { selectedMode = it; haptics.tick() }) Spacer(Modifier.height(10.dp)) @@ -128,7 +137,7 @@ fun ChatScreen(userName: String = "Bruno") { onSend = { send(input) }, 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! } } }