statusBarsPadding() was on the inner Column (only pushed content down), but the GlassSurface itself started at a fixed 12dp from the top edge — on devices with tall notches (S25 Ultra) the sidebar header sat behind the system clock/icons. Moved statusBarsPadding() to the GlassSurface modifier so the entire glass panel starts below the status bar.
552 lines
24 KiB
Kotlin
552 lines
24 KiB
Kotlin
package dev.kaizen.app.chat
|
|
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.border
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.isSystemInDarkTheme
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.fillMaxHeight
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.height
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.foundation.layout.statusBarsPadding
|
|
import androidx.compose.foundation.layout.width
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.foundation.lazy.items
|
|
import androidx.compose.foundation.text.BasicTextField
|
|
import androidx.compose.foundation.text.KeyboardActions
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.material3.AlertDialog
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.TextButton
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.draw.clip
|
|
import androidx.compose.ui.graphics.Brush
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.SolidColor
|
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
import androidx.compose.ui.text.TextStyle
|
|
import androidx.compose.ui.text.font.FontStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
import androidx.compose.ui.text.style.TextOverflow
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.sp
|
|
import dev.kaizen.app.net.ConversationSummary
|
|
import dev.kaizen.app.ui.effect.GlassSurface
|
|
import dev.kaizen.app.ui.effect.KaizenShadows
|
|
import dev.kaizen.app.ui.effect.kaizenShadow
|
|
import dev.kaizen.app.ui.effect.innerTopHighlight
|
|
import dev.kaizen.app.ui.shape.KaizenShapes
|
|
import dev.kaizen.app.R
|
|
import dev.kaizen.app.ui.theme.LocalKaizenAccent
|
|
import androidx.compose.ui.res.stringResource
|
|
import java.time.Instant
|
|
import java.time.LocalDate
|
|
import java.time.ZoneId
|
|
import java.time.format.DateTimeFormatter
|
|
import java.util.Locale
|
|
import dev.kaizen.app.ui.icon.KaizenIcons
|
|
|
|
private data class DateLabels(val today: String, val yesterday: String, val earlier: String, val pinned: String)
|
|
|
|
private fun dateLabel(updatedAt: String?, labels: DateLabels): String {
|
|
val instant = runCatching { Instant.parse(updatedAt) }.getOrNull() ?: return labels.earlier
|
|
val date = instant.atZone(ZoneId.systemDefault()).toLocalDate()
|
|
val today = LocalDate.now()
|
|
return when (date) {
|
|
today -> labels.today
|
|
today.minusDays(1) -> labels.yesterday
|
|
else -> date.format(DateTimeFormatter.ofPattern("dd. MMM yyyy", Locale.getDefault())).uppercase()
|
|
}
|
|
}
|
|
|
|
private fun groupConversations(conversations: List<ConversationSummary>, labels: DateLabels): Map<String, List<ConversationSummary>> {
|
|
val groups = LinkedHashMap<String, MutableList<ConversationSummary>>()
|
|
conversations.filter { it.pinned }.takeIf { it.isNotEmpty() }?.let { groups[labels.pinned] = it.toMutableList() }
|
|
conversations.filterNot { it.pinned }.forEach {
|
|
groups.getOrPut(dateLabel(it.updatedAt, labels)) { mutableListOf() }.add(it)
|
|
}
|
|
return groups
|
|
}
|
|
|
|
sealed interface SidebarAction {
|
|
data class Rename(val id: String, val newTitle: String) : SidebarAction
|
|
data class Delete(val id: String) : SidebarAction
|
|
data class TogglePin(val id: String, val pinned: Boolean) : SidebarAction
|
|
data class ToggleLock(val id: String, val locked: Boolean) : SidebarAction
|
|
data class GenerateTitle(val id: String) : SidebarAction
|
|
data class Duplicate(val id: String) : SidebarAction
|
|
}
|
|
|
|
@Composable
|
|
fun KaizenSidebar(
|
|
userName: String,
|
|
conversations: List<ConversationSummary>,
|
|
currentId: String?,
|
|
onClose: () -> Unit,
|
|
onNewChat: () -> Unit,
|
|
onSelectConversation: (ConversationSummary) -> Unit,
|
|
onOpenSettings: () -> Unit,
|
|
onLogout: () -> Unit,
|
|
onAction: (SidebarAction) -> Unit = {},
|
|
modifier: Modifier = Modifier
|
|
) {
|
|
val cs = MaterialTheme.colorScheme
|
|
val isDark = isSystemInDarkTheme()
|
|
val accent = LocalKaizenAccent.current
|
|
|
|
var deleteTarget by remember { mutableStateOf<ConversationSummary?>(null) }
|
|
var renameTarget by remember { mutableStateOf<ConversationSummary?>(null) }
|
|
var renameText by remember { mutableStateOf("") }
|
|
var searchQuery by remember { mutableStateOf("") }
|
|
|
|
GlassSurface(
|
|
modifier = modifier
|
|
.fillMaxHeight()
|
|
.statusBarsPadding()
|
|
.width(300.dp)
|
|
.padding(start = 12.dp, top = 8.dp, end = 4.dp, bottom = 12.dp),
|
|
shape = KaizenShapes.lg,
|
|
tintAlpha = if (isDark) 0.80f else 0.86f,
|
|
shadowStack = KaizenShadows.level3,
|
|
cornerRadius = 24.dp,
|
|
) {
|
|
Column(
|
|
Modifier
|
|
.fillMaxSize()
|
|
.padding(16.dp)
|
|
) {
|
|
// ── Header ──
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
horizontalArrangement = Arrangement.SpaceBetween,
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
Text(
|
|
stringResource(R.string.sidebar_brand),
|
|
color = cs.onBackground,
|
|
fontSize = 20.sp,
|
|
fontWeight = FontWeight.Bold,
|
|
)
|
|
Box(
|
|
modifier = Modifier
|
|
.size(34.dp)
|
|
.clip(KaizenShapes.circle)
|
|
.background(if (isDark) Color(0x1AFFFFFF) else Color(0x0A000000))
|
|
.clickable { onNewChat() },
|
|
contentAlignment = Alignment.Center
|
|
) {
|
|
Icon(KaizenIcons.Pencil, stringResource(R.string.chat_new), tint = cs.onBackground, modifier = Modifier.size(17.dp))
|
|
}
|
|
}
|
|
|
|
Spacer(Modifier.height(14.dp))
|
|
|
|
// ── Search (functional) ──
|
|
val searchBorder = remember(isDark) {
|
|
if (isDark) Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.10f), Color.White.copy(alpha = 0.03f))
|
|
) else Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.50f), Color.Black.copy(alpha = 0.04f))
|
|
)
|
|
}
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clip(KaizenShapes.pill)
|
|
.border(0.8.dp, searchBorder, KaizenShapes.pill)
|
|
.background(if (isDark) Color(0x12FFFFFF) else Color(0x08000000))
|
|
.padding(horizontal = 14.dp, vertical = 9.dp),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
Icon(KaizenIcons.Search, null, tint = cs.onSurfaceVariant.copy(alpha = 0.40f), modifier = Modifier.size(15.dp))
|
|
Spacer(Modifier.width(8.dp))
|
|
BasicTextField(
|
|
value = searchQuery,
|
|
onValueChange = { searchQuery = it },
|
|
singleLine = true,
|
|
textStyle = TextStyle(
|
|
color = cs.onBackground,
|
|
fontSize = 13.sp,
|
|
),
|
|
cursorBrush = SolidColor(cs.onSurfaceVariant),
|
|
decorationBox = { innerTextField ->
|
|
Box {
|
|
if (searchQuery.isEmpty()) {
|
|
Text(
|
|
stringResource(R.string.chat_search),
|
|
color = cs.onSurfaceVariant.copy(alpha = 0.40f),
|
|
fontSize = 13.sp,
|
|
)
|
|
}
|
|
innerTextField()
|
|
}
|
|
},
|
|
modifier = Modifier.weight(1f),
|
|
)
|
|
if (searchQuery.isNotEmpty()) {
|
|
Icon(
|
|
KaizenIcons.X,
|
|
null,
|
|
tint = cs.onSurfaceVariant.copy(alpha = 0.40f),
|
|
modifier = Modifier
|
|
.size(14.dp)
|
|
.clickable { searchQuery = "" },
|
|
)
|
|
}
|
|
}
|
|
|
|
Spacer(Modifier.height(18.dp))
|
|
|
|
// ── Conversation list ──
|
|
val filteredConversations = remember(conversations, searchQuery) {
|
|
if (searchQuery.isBlank()) conversations
|
|
else conversations.filter { it.title.contains(searchQuery, ignoreCase = true) }
|
|
}
|
|
|
|
Box(Modifier.weight(1f).fillMaxWidth()) {
|
|
if (filteredConversations.isEmpty()) {
|
|
Text(
|
|
stringResource(R.string.chat_no_conversations),
|
|
color = cs.onSurfaceVariant.copy(alpha = 0.4f),
|
|
fontSize = 13.sp,
|
|
modifier = Modifier.padding(start = 8.dp, top = 8.dp)
|
|
)
|
|
} else {
|
|
val labels = DateLabels(
|
|
today = stringResource(R.string.sidebar_today),
|
|
yesterday = stringResource(R.string.sidebar_yesterday),
|
|
earlier = stringResource(R.string.sidebar_earlier),
|
|
pinned = stringResource(R.string.sidebar_pinned),
|
|
)
|
|
val grouped = remember(filteredConversations, labels) { groupConversations(filteredConversations, labels) }
|
|
LazyColumn(
|
|
modifier = Modifier.fillMaxSize(),
|
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
) {
|
|
grouped.forEach { (dateGroup, items) ->
|
|
item(key = "header_$dateGroup") {
|
|
Text(
|
|
dateGroup,
|
|
color = cs.onSurfaceVariant.copy(alpha = 0.35f),
|
|
fontSize = 10.sp,
|
|
fontWeight = FontWeight.Bold,
|
|
letterSpacing = 1.sp,
|
|
modifier = Modifier.padding(start = 8.dp, top = 14.dp, bottom = 6.dp)
|
|
)
|
|
}
|
|
items(items, key = { it.id }) { item ->
|
|
HistoryItemRow(
|
|
item = item,
|
|
selected = item.id == currentId,
|
|
onClick = { onSelectConversation(item) },
|
|
onRename = {
|
|
renameTarget = item
|
|
renameText = item.title
|
|
},
|
|
onDelete = { deleteTarget = item },
|
|
onTogglePin = { onAction(SidebarAction.TogglePin(item.id, !item.pinned)) },
|
|
onToggleLock = { onAction(SidebarAction.ToggleLock(item.id, !item.locked)) },
|
|
onGenerateTitle = { onAction(SidebarAction.GenerateTitle(item.id)) },
|
|
onDuplicate = { onAction(SidebarAction.Duplicate(item.id)) },
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Spacer(Modifier.height(12.dp))
|
|
|
|
// ── User card ──
|
|
val cardBorder = remember(isDark) {
|
|
if (isDark) Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.12f), Color.White.copy(alpha = 0.03f))
|
|
) else Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.50f), Color.Black.copy(alpha = 0.04f))
|
|
)
|
|
}
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clip(KaizenShapes.md)
|
|
.kaizenShadow(stack = KaizenShadows.level1, cornerRadius = 12.dp)
|
|
.background(if (isDark) Color(0x14FFFFFF) else Color(0x0A000000))
|
|
.border(0.8.dp, cardBorder, KaizenShapes.md)
|
|
.innerTopHighlight()
|
|
.clickable { onOpenSettings() }
|
|
.padding(horizontal = 12.dp, vertical = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
Box(
|
|
modifier = Modifier
|
|
.size(30.dp)
|
|
.clip(KaizenShapes.circle)
|
|
.background(
|
|
Brush.verticalGradient(
|
|
listOf(accent.primary.copy(alpha = 0.8f), accent.primary.copy(alpha = 0.6f))
|
|
)
|
|
),
|
|
contentAlignment = Alignment.Center
|
|
) {
|
|
Text(
|
|
userName.firstOrNull()?.toString()?.uppercase() ?: "A",
|
|
color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.Bold
|
|
)
|
|
}
|
|
Spacer(Modifier.width(10.dp))
|
|
Text(
|
|
userName,
|
|
color = cs.onBackground,
|
|
fontSize = 14.sp,
|
|
fontWeight = FontWeight.Medium,
|
|
modifier = Modifier.weight(1f),
|
|
)
|
|
Icon(
|
|
KaizenIcons.Settings,
|
|
stringResource(R.string.sidebar_settings),
|
|
tint = cs.onSurfaceVariant.copy(alpha = 0.45f),
|
|
modifier = Modifier.size(17.dp),
|
|
)
|
|
Spacer(Modifier.width(6.dp))
|
|
Box(
|
|
modifier = Modifier
|
|
.size(28.dp)
|
|
.clip(KaizenShapes.circle)
|
|
.clickable { onLogout() },
|
|
contentAlignment = Alignment.Center,
|
|
) {
|
|
Icon(
|
|
KaizenIcons.LogOut,
|
|
stringResource(R.string.sidebar_logout),
|
|
tint = cs.onSurfaceVariant.copy(alpha = 0.45f),
|
|
modifier = Modifier.size(15.dp),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete confirmation dialog
|
|
deleteTarget?.let { target ->
|
|
AlertDialog(
|
|
onDismissRequest = { deleteTarget = null },
|
|
title = { Text(stringResource(R.string.sidebar_delete)) },
|
|
text = { Text(stringResource(R.string.sidebar_delete_confirm)) },
|
|
confirmButton = {
|
|
TextButton(onClick = {
|
|
onAction(SidebarAction.Delete(target.id))
|
|
deleteTarget = null
|
|
}) {
|
|
Text(stringResource(R.string.sidebar_confirm), color = Color(0xFFEF4444))
|
|
}
|
|
},
|
|
dismissButton = {
|
|
TextButton(onClick = { deleteTarget = null }) {
|
|
Text(stringResource(R.string.sidebar_cancel))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
// Rename dialog
|
|
renameTarget?.let { target ->
|
|
val focusManager = LocalFocusManager.current
|
|
AlertDialog(
|
|
onDismissRequest = { renameTarget = null },
|
|
title = { Text(stringResource(R.string.sidebar_rename_title)) },
|
|
text = {
|
|
BasicTextField(
|
|
value = renameText,
|
|
onValueChange = { renameText = it },
|
|
singleLine = true,
|
|
textStyle = TextStyle(
|
|
color = cs.onBackground,
|
|
fontSize = 15.sp,
|
|
),
|
|
cursorBrush = SolidColor(cs.onBackground),
|
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
keyboardActions = KeyboardActions(onDone = {
|
|
focusManager.clearFocus()
|
|
if (renameText.isNotBlank()) {
|
|
onAction(SidebarAction.Rename(target.id, renameText.trim()))
|
|
}
|
|
renameTarget = null
|
|
}),
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clip(KaizenShapes.sm)
|
|
.background(if (isDark) Color(0x12FFFFFF) else Color(0x08000000))
|
|
.padding(horizontal = 14.dp, vertical = 12.dp),
|
|
)
|
|
},
|
|
confirmButton = {
|
|
TextButton(onClick = {
|
|
if (renameText.isNotBlank()) {
|
|
onAction(SidebarAction.Rename(target.id, renameText.trim()))
|
|
}
|
|
renameTarget = null
|
|
}) {
|
|
Text(stringResource(R.string.sidebar_save))
|
|
}
|
|
},
|
|
dismissButton = {
|
|
TextButton(onClick = { renameTarget = null }) {
|
|
Text(stringResource(R.string.sidebar_cancel))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun HistoryItemRow(
|
|
item: ConversationSummary,
|
|
selected: Boolean,
|
|
onClick: () -> Unit,
|
|
onRename: () -> Unit,
|
|
onDelete: () -> Unit,
|
|
onTogglePin: () -> Unit,
|
|
onToggleLock: () -> Unit,
|
|
onGenerateTitle: () -> Unit,
|
|
onDuplicate: () -> Unit,
|
|
) {
|
|
val cs = MaterialTheme.colorScheme
|
|
val isDark = isSystemInDarkTheme()
|
|
val accent = LocalKaizenAccent.current
|
|
|
|
var showMenu by remember { mutableStateOf(false) }
|
|
|
|
val activeBorder = remember(isDark, accent) {
|
|
if (isDark) Brush.verticalGradient(
|
|
listOf(accent.primary.copy(alpha = 0.25f), accent.primary.copy(alpha = 0.06f))
|
|
) else Brush.verticalGradient(
|
|
listOf(accent.primary.copy(alpha = 0.20f), accent.primary.copy(alpha = 0.05f))
|
|
)
|
|
}
|
|
val inactiveBorder = remember(isDark) {
|
|
if (isDark) Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.07f), Color.White.copy(alpha = 0.02f))
|
|
) else Brush.verticalGradient(
|
|
listOf(Color.White.copy(alpha = 0.35f), Color.Black.copy(alpha = 0.03f))
|
|
)
|
|
}
|
|
|
|
Box(modifier = Modifier.padding(horizontal = 2.dp)) {
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clip(KaizenShapes.md)
|
|
.then(
|
|
if (selected) {
|
|
Modifier
|
|
.kaizenShadow(stack = KaizenShadows.level1, cornerRadius = 16.dp)
|
|
.background(accent.primary.copy(alpha = if (isDark) 0.12f else 0.08f))
|
|
.border(0.8.dp, activeBorder, KaizenShapes.md)
|
|
} else {
|
|
Modifier
|
|
.background(if (isDark) Color(0x08FFFFFF) else Color(0x05000000))
|
|
.border(0.5.dp, inactiveBorder, KaizenShapes.md)
|
|
}
|
|
)
|
|
.clickable { onClick() }
|
|
.padding(start = 12.dp, end = 4.dp, top = 10.dp, bottom = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
if (item.pinned) {
|
|
Icon(KaizenIcons.Star, null, tint = accent.primary.copy(alpha = 0.7f), modifier = Modifier.size(13.dp))
|
|
Spacer(Modifier.width(6.dp))
|
|
}
|
|
Text(
|
|
text = if (item.locked) stringResource(R.string.chat_locked) else item.title,
|
|
color = if (item.locked) cs.onSurfaceVariant.copy(alpha = 0.45f) else cs.onBackground.copy(alpha = if (selected) 0.95f else 0.80f),
|
|
fontSize = 13.sp,
|
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
|
|
fontStyle = if (item.locked) FontStyle.Italic else FontStyle.Normal,
|
|
maxLines = 1,
|
|
overflow = TextOverflow.Ellipsis,
|
|
modifier = Modifier.weight(1f),
|
|
)
|
|
if (item.locked) {
|
|
Icon(KaizenIcons.Lock, null, tint = accent.primary.copy(alpha = 0.5f), modifier = Modifier.size(13.dp))
|
|
Spacer(Modifier.width(2.dp))
|
|
}
|
|
Box(
|
|
modifier = Modifier
|
|
.size(28.dp)
|
|
.clip(KaizenShapes.circle)
|
|
.clickable { showMenu = true },
|
|
contentAlignment = Alignment.Center,
|
|
) {
|
|
Icon(
|
|
KaizenIcons.MoreVertical,
|
|
stringResource(R.string.sidebar_more),
|
|
tint = cs.onSurfaceVariant.copy(alpha = 0.30f),
|
|
modifier = Modifier.size(15.dp),
|
|
)
|
|
}
|
|
}
|
|
|
|
androidx.compose.material3.DropdownMenu(
|
|
expanded = showMenu,
|
|
onDismissRequest = { showMenu = false },
|
|
containerColor = if (isDark) Color(0xFF1A2030) else Color(0xFFF8F9FB),
|
|
shadowElevation = 16.dp,
|
|
shape = KaizenShapes.md,
|
|
) {
|
|
if (!item.locked) {
|
|
MenuRow(KaizenIcons.Pencil, stringResource(R.string.sidebar_rename), cs.onBackground) { showMenu = false; onRename() }
|
|
}
|
|
if (!item.titleGenerated) {
|
|
MenuRow(KaizenIcons.Sparkles, stringResource(R.string.sidebar_generate_title), cs.onBackground) { showMenu = false; onGenerateTitle() }
|
|
}
|
|
MenuRow(KaizenIcons.Pin, stringResource(if (item.pinned) R.string.sidebar_unpin else R.string.sidebar_pin), cs.onBackground) { showMenu = false; onTogglePin() }
|
|
MenuRow(KaizenIcons.Copy, stringResource(R.string.sidebar_duplicate), cs.onBackground) { showMenu = false; onDuplicate() }
|
|
MenuRow(
|
|
if (item.locked) KaizenIcons.LockOpen else KaizenIcons.Lock,
|
|
stringResource(if (item.locked) R.string.sidebar_unlock else R.string.sidebar_lock),
|
|
cs.onBackground,
|
|
) { showMenu = false; onToggleLock() }
|
|
// Separator
|
|
Box(
|
|
Modifier
|
|
.fillMaxWidth()
|
|
.padding(horizontal = 16.dp, vertical = 4.dp)
|
|
.height(0.5.dp)
|
|
.background(if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f))
|
|
)
|
|
MenuRow(KaizenIcons.Trash, 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 = 11.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
) {
|
|
Icon(icon, null, tint = tint.copy(alpha = 0.6f), modifier = Modifier.size(16.dp))
|
|
Spacer(Modifier.width(12.dp))
|
|
Text(label, color = tint.copy(alpha = 0.9f), fontSize = 13.5.sp, fontWeight = FontWeight.Medium)
|
|
}
|
|
}
|