feat(ui): implement tablet optimizations and gemini-style dynamic keyboard suggestion-hiding
This commit is contained in:
parent
e13f6798db
commit
e3ea6d241f
2 changed files with 34 additions and 12 deletions
|
|
@ -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)
|
||||||
|
|
||||||
|
if (!isKeyboardOpen) {
|
||||||
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(142.dp)) // Clear the floating bottom dock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue