fix scroll buttons + restyle sidebar context menu

Scroll buttons: only one shown at a time based on direction
(up when scrolled down, down when scrolled up), hidden during
streaming. Mutually exclusive instead of both visible.

Sidebar menu: replaced default Material3 DropdownMenu/MenuItem
with custom styled menu — dark surface color (1A2030 dark,
F8F9FB light), KaizenShapes.md rounded corners, 12dp shadow
elevation, custom MenuRow composable with proper spacing.
This commit is contained in:
Bruno Deanoz 2026-06-22 01:05:35 +02:00
parent f62cc4b7ac
commit fb269731b7
2 changed files with 64 additions and 78 deletions

View file

@ -569,27 +569,24 @@ fun ChatScreen(
}
}
// Scroll buttons
val showScrollUp by remember { derivedStateOf { listState.firstVisibleItemIndex > 2 } }
val showScrollDown by remember { derivedStateOf {
if (!isStreaming) {
val atTop by remember { derivedStateOf { listState.firstVisibleItemIndex <= 1 } }
val atBottom by remember { derivedStateOf {
val info = listState.layoutInfo
info.visibleItemsInfo.lastOrNull()?.index != info.totalItemsCount - 1
info.visibleItemsInfo.lastOrNull()?.index == info.totalItemsCount - 1
}}
val canScrollUp by remember { derivedStateOf { listState.canScrollBackward } }
val canScrollDown by remember { derivedStateOf { listState.canScrollForward } }
if (showScrollUp || showScrollDown) {
Column(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (showScrollUp) {
if (!atTop && canScrollUp) {
GlassSurface(
shape = KaizenShapes.circle,
tintAlpha = GlassTiers.pill(isSystemInDarkTheme()),
shadowStack = KaizenShadows.level1,
cornerRadius = 18.dp,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 8.dp, bottom = 24.dp)
.size(36.dp)
.clickable { scope.launch { listState.animateScrollToItem(0) } },
) {
@ -597,14 +594,15 @@ fun ChatScreen(
Icon(Icons.Rounded.KeyboardArrowUp, null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.size(20.dp))
}
}
}
if (showScrollDown) {
} else if (!atBottom && canScrollDown) {
GlassSurface(
shape = KaizenShapes.circle,
tintAlpha = GlassTiers.pill(isSystemInDarkTheme()),
shadowStack = KaizenShadows.level1,
cornerRadius = 18.dp,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 8.dp, top = 24.dp)
.size(36.dp)
.clickable { scope.launch { listState.animateScrollToItem(messages.size - 1) } },
) {
@ -615,7 +613,6 @@ fun ChatScreen(
}
}
}
}
// 2. Floating Top Menu Button
val scrimColor = MaterialTheme.colorScheme.background

View file

@ -34,8 +34,7 @@ import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -423,48 +422,38 @@ private fun HistoryItemRow(
}
}
DropdownMenu(
androidx.compose.material3.DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
containerColor = if (isDark) Color(0xFF1A2030) else Color(0xFFF8F9FB),
shadowElevation = 12.dp,
shape = KaizenShapes.md,
) {
if (!item.locked) {
DropdownMenuItem(
text = { Text(stringResource(R.string.sidebar_rename), fontSize = 14.sp) },
onClick = { showMenu = false; onRename() },
leadingIcon = { Icon(Icons.Rounded.Edit, null, modifier = Modifier.size(18.dp)) },
)
MenuRow(Icons.Rounded.Edit, stringResource(R.string.sidebar_rename), cs.onBackground) { showMenu = false; onRename() }
}
DropdownMenuItem(
text = {
Text(
stringResource(if (item.pinned) R.string.sidebar_unpin else R.string.sidebar_pin),
fontSize = 14.sp,
)
},
onClick = { showMenu = false; onTogglePin() },
leadingIcon = { Icon(Icons.Rounded.PushPin, null, modifier = Modifier.size(18.dp)) },
)
DropdownMenuItem(
text = {
Text(
stringResource(if (item.locked) R.string.sidebar_unlock else R.string.sidebar_lock),
fontSize = 14.sp,
)
},
onClick = { showMenu = false; onToggleLock() },
leadingIcon = {
Icon(
MenuRow(Icons.Rounded.PushPin, stringResource(if (item.pinned) R.string.sidebar_unpin else R.string.sidebar_pin), cs.onBackground) { showMenu = false; onTogglePin() }
MenuRow(
if (item.locked) Icons.Rounded.LockOpen else Icons.Rounded.Lock,
null,
modifier = Modifier.size(18.dp),
)
},
)
DropdownMenuItem(
text = { Text(stringResource(R.string.sidebar_delete), fontSize = 14.sp, color = Color(0xFFEF4444)) },
onClick = { showMenu = false; onDelete() },
leadingIcon = { Icon(Icons.Rounded.Delete, null, tint = Color(0xFFEF4444), modifier = Modifier.size(18.dp)) },
)
stringResource(if (item.locked) R.string.sidebar_unlock else R.string.sidebar_lock),
cs.onBackground,
) { showMenu = false; onToggleLock() }
MenuRow(Icons.Rounded.Delete, stringResource(R.string.sidebar_delete), Color(0xFFEF4444)) { showMenu = false; onDelete() }
}
}
}
@Composable
private fun MenuRow(icon: ImageVector, label: String, tint: Color, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(icon, null, tint = tint.copy(alpha = 0.7f), modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
Text(label, color = tint, fontSize = 14.sp, fontWeight = FontWeight.Medium)
}
}