feat: KaizenIcons system — Lucide base, full Material Icons migration

Introduces a centralized icon registry (ui/icon/KaizenIcons.kt) that
decouples every UI component from the underlying icon library. All 10
source files migrated from Material Icons Rounded to KaizenIcons backed
by Lucide (1.5px stroke, same library as the web frontend).

Architecture:
- KaizenIcons object with semantic getters (Send, Lock, Brain, etc.)
- Grouped by function (Navigation, Chat, Modes, Files, Settings, etc.)
- Custom brand section ready for hand-drawn SVG overrides
- One-line swap: change the getter, every usage updates
- Zero Material Icon imports remaining in app source

Dependency: com.composables:icons-lucide-android:1.0.0
This commit is contained in:
Bruno Deanoz 2026-06-22 14:11:34 +02:00
parent 1362daee53
commit 68721d8ddf
13 changed files with 187 additions and 147 deletions

View file

@ -46,6 +46,7 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.compose.icons.lucide)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)

View file

@ -39,10 +39,6 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.CameraAlt
import androidx.compose.material.icons.rounded.Call
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -79,28 +75,12 @@ import java.time.format.DateTimeFormatter
import java.util.Locale
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.material.icons.rounded.AudioFile
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Code
import androidx.compose.material.icons.rounded.Image
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
import androidx.compose.material.icons.rounded.PictureAsPdf
import androidx.compose.material.icons.rounded.VideoFile
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.material.icons.rounded.Bolt
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Mic
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.ui.platform.LocalClipboard
import dev.kaizen.app.haptics.rememberHaptics
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Psychology
import androidx.compose.material.icons.rounded.Tune
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
@ -111,6 +91,7 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable
fun KaizenHeader(modifier: Modifier = Modifier) {
@ -199,11 +180,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Rounded.Psychology, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Icon(KaizenIcons.Brain, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(6.dp))
Text(stringResource(reasoningPreset.label), color = cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.width(4.dp))
Icon(Icons.Rounded.ExpandMore, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
Icon(KaizenIcons.ChevronDown, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
}
DropdownMenu(expanded = showReasoningMenu, onDismissRequest = { showReasoningMenu = false }) {
ReasoningPreset.entries.forEach { preset ->
@ -234,11 +215,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Rounded.Tune, null, tint = if (anyEnabled) accent.primary else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Icon(KaizenIcons.SlidersHorizontal, null, tint = if (anyEnabled) accent.primary else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(6.dp))
Text(stringResource(R.string.mode_sampling), color = if (anyEnabled) cs.onBackground else cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.width(4.dp))
Icon(Icons.Rounded.ExpandMore, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
Icon(KaizenIcons.ChevronDown, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
}
DropdownMenu(expanded = showSamplingPopup, onDismissRequest = { showSamplingPopup = false }) {
Column(Modifier.padding(horizontal = 16.dp, vertical = 8.dp).width(240.dp)) {
@ -332,7 +313,7 @@ fun TokenBadge(used: Int, contextLength: Int, modifier: Modifier = Modifier) {
Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Rounded.Bolt, null, tint = accent.primary, modifier = Modifier.size(14.dp))
Icon(KaizenIcons.Zap, null, tint = accent.primary, modifier = Modifier.size(14.dp))
Spacer(Modifier.width(4.dp))
Text(
"${formatK(used)} / ${formatK(contextLength)}",
@ -416,7 +397,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
MessageActionButton(
icon = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
icon = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) {
haptics.tick()
@ -428,7 +409,7 @@ fun MessageRow(
}
}
onDelete?.let { del ->
MessageActionButton(Icons.Rounded.Delete, Color(0xFFEF4444).copy(alpha = 0.6f)) {
MessageActionButton(KaizenIcons.Trash, Color(0xFFEF4444).copy(alpha = 0.6f)) {
haptics.tick(); del(message.wireId)
}
}
@ -469,7 +450,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
MessageActionButton(
icon = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
icon = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) {
haptics.tick()
@ -481,12 +462,12 @@ fun MessageRow(
}
}
onRegenerate?.let { regen ->
MessageActionButton(Icons.Rounded.Refresh, cs.onSurfaceVariant.copy(alpha = 0.35f)) {
MessageActionButton(KaizenIcons.RefreshCw, cs.onSurfaceVariant.copy(alpha = 0.35f)) {
haptics.tick(); regen(message.wireId)
}
}
onDelete?.let { del ->
MessageActionButton(Icons.Rounded.Delete, Color(0xFFEF4444).copy(alpha = 0.6f)) {
MessageActionButton(KaizenIcons.Trash, Color(0xFFEF4444).copy(alpha = 0.6f)) {
haptics.tick(); del(message.wireId)
}
}
@ -575,7 +556,7 @@ fun ChatInput(
}
Row(Modifier.padding(start = 6.dp, end = 8.dp, top = 10.dp, bottom = 10.dp), verticalAlignment = Alignment.Bottom) {
Box(Modifier.size(42.dp).clip(KaizenShapes.circle).clickable(onClick = onAddClick), contentAlignment = Alignment.Center) {
Icon(Icons.Rounded.Add, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
Icon(KaizenIcons.Plus, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
}
Box(Modifier.weight(1f).heightIn(min = 42.dp).padding(horizontal = 6.dp), contentAlignment = Alignment.CenterStart) {
if (value.isEmpty()) Text(stringResource(R.string.chat_input_placeholder), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 15.sp)
@ -591,7 +572,7 @@ fun ChatInput(
SendButton(enabled = canSend, onClick = onSend)
} else {
Box(Modifier.size(44.dp).clip(KaizenShapes.circle), contentAlignment = Alignment.Center) {
Icon(Icons.Rounded.Mic, stringResource(R.string.chat_call), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
Icon(KaizenIcons.Mic, stringResource(R.string.chat_call), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
}
}
}
@ -611,11 +592,11 @@ private fun PendingFileChip(pf: PendingFile, onRemove: () -> Unit) {
NetworkImage(url = pf.uploaded.url, contentDescription = pf.name, modifier = Modifier.fillMaxSize().clip(KaizenShapes.xs))
} else {
val icon = when {
pf.mimeType.startsWith("image/") -> Icons.Rounded.Image
pf.mimeType.startsWith("video/") -> Icons.Rounded.VideoFile
pf.mimeType.startsWith("audio/") -> Icons.Rounded.AudioFile
pf.mimeType == "application/pdf" -> Icons.Rounded.PictureAsPdf
else -> Icons.AutoMirrored.Rounded.InsertDriveFile
pf.mimeType.startsWith("image/") -> KaizenIcons.Image
pf.mimeType.startsWith("video/") -> KaizenIcons.FileVideo
pf.mimeType.startsWith("audio/") -> KaizenIcons.FileAudio
pf.mimeType == "application/pdf" -> KaizenIcons.FileText
else -> KaizenIcons.File
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(20.dp))
@ -629,14 +610,14 @@ private fun PendingFileChip(pf: PendingFile, onRemove: () -> Unit) {
}
if (pf.error != null) {
Box(Modifier.fillMaxSize().background(Color(0xFFDC2626).copy(alpha = 0.3f)), contentAlignment = Alignment.Center) {
Icon(Icons.Rounded.Close, stringResource(R.string.chat_error), tint = Color.White, modifier = Modifier.size(18.dp))
Icon(KaizenIcons.X, stringResource(R.string.chat_error), tint = Color.White, modifier = Modifier.size(18.dp))
}
}
Box(
Modifier.align(Alignment.TopEnd).padding(2.dp).size(18.dp).clip(KaizenShapes.circle).background(Color.Black.copy(alpha = 0.5f)).clickable(onClick = onRemove),
contentAlignment = Alignment.Center,
) {
Icon(Icons.Rounded.Close, stringResource(R.string.chat_remove), tint = Color.White, modifier = Modifier.size(12.dp))
Icon(KaizenIcons.X, stringResource(R.string.chat_remove), tint = Color.White, modifier = Modifier.size(12.dp))
}
}
}
@ -718,11 +699,11 @@ private fun FileChip(attachment: Attachment) {
val isDark = isSystemInDarkTheme()
val bg = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
val icon = when (attachment.kind) {
"pdf" -> Icons.Rounded.PictureAsPdf
"audio" -> Icons.Rounded.AudioFile
"video" -> Icons.Rounded.VideoFile
"code" -> Icons.Rounded.Code
else -> Icons.AutoMirrored.Rounded.InsertDriveFile
"pdf" -> KaizenIcons.FileText
"audio" -> KaizenIcons.FileAudio
"video" -> KaizenIcons.FileVideo
"code" -> KaizenIcons.Code
else -> KaizenIcons.File
}
Row(Modifier.clip(KaizenShapes.xs).background(bg).padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically) {
Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
@ -746,9 +727,9 @@ fun AttachmentMenu(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.Start,
) {
AttachPill(Icons.Rounded.CameraAlt, stringResource(R.string.attach_camera), accent.primary, isDark, onCamera)
AttachPill(Icons.Rounded.Image, stringResource(R.string.attach_gallery), Color(0xFF6366F1), isDark, onGallery)
AttachPill(Icons.AutoMirrored.Rounded.InsertDriveFile, stringResource(R.string.attach_file), Color(0xFF10B981), isDark, onFile)
AttachPill(KaizenIcons.Camera, stringResource(R.string.attach_camera), accent.primary, isDark, onCamera)
AttachPill(KaizenIcons.Image, stringResource(R.string.attach_gallery), Color(0xFF6366F1), isDark, onGallery)
AttachPill(KaizenIcons.File, stringResource(R.string.attach_file), Color(0xFF10B981), isDark, onFile)
}
}

View file

@ -1,13 +1,5 @@
package dev.kaizen.app.chat
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Adjust
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.Image
import androidx.compose.material.icons.rounded.Language
import androidx.compose.material.icons.rounded.Movie
import androidx.compose.material.icons.rounded.MusicNote
import androidx.compose.material.icons.rounded.Tune
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
@ -15,6 +7,7 @@ import dev.kaizen.app.R
import dev.kaizen.app.net.Attachment
import dev.kaizen.app.net.SearchSource
import dev.kaizen.app.net.ToolEvent
import dev.kaizen.app.ui.icon.KaizenIcons
enum class Role { User, Assistant }
@ -54,21 +47,21 @@ data class Message(
data class Suggestion(val labelRes: Int, val promptRes: Int, val icon: ImageVector)
val suggestions = listOf(
Suggestion(R.string.suggest_brainstorm, R.string.suggest_brainstorm_prompt, Icons.Rounded.AutoAwesome),
Suggestion(R.string.suggest_image, R.string.suggest_image_prompt, Icons.Rounded.Image),
Suggestion(R.string.suggest_video, R.string.suggest_video_prompt, Icons.Rounded.Movie),
Suggestion(R.string.suggest_web, R.string.suggest_web_prompt, Icons.Rounded.Language),
Suggestion(R.string.suggest_brainstorm, R.string.suggest_brainstorm_prompt, KaizenIcons.Sparkles),
Suggestion(R.string.suggest_image, R.string.suggest_image_prompt, KaizenIcons.Image),
Suggestion(R.string.suggest_video, R.string.suggest_video_prompt, KaizenIcons.Video),
Suggestion(R.string.suggest_web, R.string.suggest_web_prompt, KaizenIcons.Globe),
)
data class ChatMode(val labelRes: Int, val icon: ImageVector)
val chatModes = listOf(
ChatMode(R.string.mode_standard, Icons.Rounded.Adjust),
ChatMode(R.string.mode_sampling, Icons.Rounded.Tune),
ChatMode(R.string.mode_image, Icons.Rounded.Image),
ChatMode(R.string.mode_search, Icons.Rounded.Language),
ChatMode(R.string.mode_video, Icons.Rounded.Movie),
ChatMode(R.string.mode_audio, Icons.Rounded.MusicNote),
ChatMode(R.string.mode_standard, KaizenIcons.CircleDot),
ChatMode(R.string.mode_sampling, KaizenIcons.SlidersHorizontal),
ChatMode(R.string.mode_image, KaizenIcons.Image),
ChatMode(R.string.mode_search, KaizenIcons.Globe),
ChatMode(R.string.mode_video, KaizenIcons.Video),
ChatMode(R.string.mode_audio, KaizenIcons.Music),
)
@Composable

View file

@ -29,10 +29,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material.icons.rounded.Menu
import dev.kaizen.app.ui.icon.KaizenIcons
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -665,7 +662,7 @@ fun ChatScreen(
.clickable { scope.launch { listState.animateScrollToItem(0) } },
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(Icons.Rounded.KeyboardArrowUp, null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.size(20.dp))
Icon(KaizenIcons.ArrowUp, null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.size(20.dp))
}
}
} else if (!atBottom && canScrollDown) {
@ -681,7 +678,7 @@ fun ChatScreen(
.clickable { scope.launch { listState.animateScrollToItem(messages.size - 1) } },
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(Icons.Rounded.KeyboardArrowDown, null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.size(20.dp))
Icon(KaizenIcons.ArrowDown, null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.size(20.dp))
}
}
}
@ -730,7 +727,7 @@ fun ChatScreen(
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Rounded.Menu,
imageVector = KaizenIcons.Menu,
contentDescription = context.getString(R.string.chat_open_menu),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(17.dp),

View file

@ -19,9 +19,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -49,6 +46,7 @@ import androidx.compose.ui.unit.sp
import dev.kaizen.app.haptics.rememberHaptics
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import dev.kaizen.app.ui.icon.KaizenIcons
private sealed interface MdBlock {
data class Paragraph(val text: String) : MdBlock
@ -526,7 +524,7 @@ private fun CopyButton(code: String) {
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
imageVector = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
contentDescription = "Kopieren",
tint = if (copied) Color(0xFF22C55E) else cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(14.dp),

View file

@ -8,8 +8,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -25,6 +23,7 @@ import dev.kaizen.app.R
import androidx.compose.ui.res.stringResource
import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.haptics.rememberHaptics
import dev.kaizen.app.ui.icon.KaizenIcons
/** Human label + composite-id resolver for the top pill — falls back to a de-prefixed id. */
fun modelDisplayName(modelId: String, models: List<KaizenModel>): String =
@ -52,7 +51,7 @@ fun ModelPill(label: String, onClick: () -> Unit, modifier: Modifier = Modifier)
maxLines = 1,
)
Icon(
Icons.Rounded.ExpandMore,
KaizenIcons.ChevronDown,
contentDescription = null,
tint = cs.onSurfaceVariant,
modifier = Modifier.size(18.dp),
@ -139,7 +138,7 @@ fun ModelSheet(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Rounded.Search,
KaizenIcons.Search,
contentDescription = null,
tint = cs.onSurfaceVariant.copy(alpha = 0.6f),
modifier = Modifier.size(18.dp)
@ -167,7 +166,7 @@ fun ModelSheet(
}
if (query.isNotEmpty()) {
Icon(
Icons.Rounded.Close,
KaizenIcons.X,
contentDescription = stringResource(R.string.model_search_clear),
tint = cs.onSurfaceVariant.copy(alpha = 0.6f),
modifier = Modifier
@ -294,7 +293,7 @@ private fun ModelRow(
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = if (favorite) Icons.Rounded.Star else Icons.Rounded.StarBorder,
imageVector = if (favorite) KaizenIcons.Star else KaizenIcons.StarOff,
contentDescription = stringResource(R.string.model_favorite),
tint = if (favorite) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.35f),
modifier = Modifier.size(20.dp),
@ -304,7 +303,7 @@ private fun ModelRow(
if (selected) {
Spacer(Modifier.size(4.dp))
Icon(
Icons.Rounded.Check,
KaizenIcons.Check,
contentDescription = stringResource(R.string.model_selected),
tint = accent.primary,
modifier = Modifier.size(20.dp)

View file

@ -22,17 +22,6 @@ 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.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Logout
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.LockOpen
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.PushPin
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.ui.graphics.vector.ImageVector
import androidx.compose.material3.Icon
@ -70,6 +59,7 @@ 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)
@ -156,7 +146,7 @@ fun KaizenSidebar(
.clickable { onNewChat() },
contentAlignment = Alignment.Center
) {
Icon(Icons.Rounded.Edit, stringResource(R.string.chat_new), tint = cs.onBackground, modifier = Modifier.size(17.dp))
Icon(KaizenIcons.Pencil, stringResource(R.string.chat_new), tint = cs.onBackground, modifier = Modifier.size(17.dp))
}
}
@ -171,7 +161,7 @@ fun KaizenSidebar(
.padding(horizontal = 14.dp, vertical = 9.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Search, null, tint = cs.onSurfaceVariant.copy(alpha = 0.45f), modifier = Modifier.size(16.dp))
Icon(KaizenIcons.Search, null, tint = cs.onSurfaceVariant.copy(alpha = 0.45f), modifier = Modifier.size(16.dp))
Spacer(Modifier.width(8.dp))
Text(stringResource(R.string.chat_search), color = cs.onSurfaceVariant.copy(alpha = 0.45f), fontSize = 13.sp)
}
@ -261,7 +251,7 @@ fun KaizenSidebar(
modifier = Modifier.weight(1f),
)
Icon(
Icons.Rounded.Settings,
KaizenIcons.Settings,
stringResource(R.string.sidebar_settings),
tint = cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(18.dp),
@ -275,7 +265,7 @@ fun KaizenSidebar(
contentAlignment = Alignment.Center,
) {
Icon(
Icons.AutoMirrored.Rounded.Logout,
KaizenIcons.LogOut,
stringResource(R.string.sidebar_logout),
tint = cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(16.dp),
@ -389,7 +379,7 @@ private fun HistoryItemRow(
verticalAlignment = Alignment.CenterVertically
) {
if (item.pinned) {
Icon(Icons.Rounded.Star, null, tint = accent.primary.copy(alpha = 0.7f), modifier = Modifier.size(13.dp))
Icon(KaizenIcons.Star, null, tint = accent.primary.copy(alpha = 0.7f), modifier = Modifier.size(13.dp))
Spacer(Modifier.width(6.dp))
}
Text(
@ -403,7 +393,7 @@ private fun HistoryItemRow(
modifier = Modifier.weight(1f),
)
if (item.locked) {
Icon(Icons.Rounded.Lock, null, tint = accent.primary.copy(alpha = 0.5f), modifier = Modifier.size(13.dp))
Icon(KaizenIcons.Lock, null, tint = accent.primary.copy(alpha = 0.5f), modifier = Modifier.size(13.dp))
Spacer(Modifier.width(2.dp))
}
Box(
@ -414,7 +404,7 @@ private fun HistoryItemRow(
contentAlignment = Alignment.Center,
) {
Icon(
Icons.Rounded.MoreVert,
KaizenIcons.MoreVertical,
stringResource(R.string.sidebar_more),
tint = cs.onSurfaceVariant.copy(alpha = 0.35f),
modifier = Modifier.size(16.dp),
@ -430,15 +420,15 @@ private fun HistoryItemRow(
shape = KaizenShapes.md,
) {
if (!item.locked) {
MenuRow(Icons.Rounded.Edit, stringResource(R.string.sidebar_rename), cs.onBackground) { showMenu = false; onRename() }
MenuRow(KaizenIcons.Pencil, stringResource(R.string.sidebar_rename), cs.onBackground) { showMenu = false; onRename() }
}
MenuRow(Icons.Rounded.PushPin, stringResource(if (item.pinned) R.string.sidebar_unpin else R.string.sidebar_pin), cs.onBackground) { showMenu = false; onTogglePin() }
MenuRow(KaizenIcons.Pin, 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,
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() }
MenuRow(Icons.Rounded.Delete, stringResource(R.string.sidebar_delete), Color(0xFFEF4444)) { showMenu = false; onDelete() }
MenuRow(KaizenIcons.Trash, stringResource(R.string.sidebar_delete), Color(0xFFEF4444)) { showMenu = false; onDelete() }
}
}
}

View file

@ -17,16 +17,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Error
import androidx.compose.material.icons.rounded.ExpandLess
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Language
import androidx.compose.material.icons.rounded.Psychology
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.Build
import androidx.compose.material.icons.rounded.Description
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -50,6 +40,7 @@ import dev.kaizen.app.ui.shape.KaizenShapes
import dev.kaizen.app.ui.theme.LocalKaizenAccent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable
fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
@ -75,7 +66,7 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Rounded.Psychology, null, tint = accent.primary, modifier = Modifier.size(16.dp))
Icon(KaizenIcons.Brain, null, tint = accent.primary, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(8.dp))
Text(
stringResource(R.string.stream_reasoning),
@ -85,7 +76,7 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
modifier = Modifier.weight(1f),
)
Icon(
if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
if (expanded) KaizenIcons.ChevronUp else KaizenIcons.ChevronDown,
null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp),
)
}
@ -134,21 +125,21 @@ fun ToolEventsBlock(tools: List<ToolEvent>, streaming: Boolean = false) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
if (analyzing != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Rounded.Description, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(KaizenIcons.FileText, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Spacer(Modifier.width(6.dp))
Text(stringResource(R.string.stream_tool_analyzing, analyzing!!), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp)
}
}
if (thinking && analyzing == null && completed.isEmpty() && inProgress.isEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Rounded.AutoAwesome, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(KaizenIcons.Sparkles, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Spacer(Modifier.width(6.dp))
Text(stringResource(R.string.stream_tool_thinking), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp)
}
}
completed.forEach { tool ->
Row(verticalAlignment = Alignment.CenterVertically) {
val icon = if (tool.type == "error") Icons.Rounded.Error else Icons.Rounded.Check
val icon = if (tool.type == "error") KaizenIcons.AlertCircle else KaizenIcons.Check
val tint = if (tool.type == "error") Color(0xFFEF4444) else Color(0xFF10B981)
Icon(icon, null, tint = tint, modifier = Modifier.size(14.dp))
Spacer(Modifier.width(6.dp))
@ -161,7 +152,7 @@ fun ToolEventsBlock(tools: List<ToolEvent>, streaming: Boolean = false) {
}
inProgress.forEach { name ->
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Rounded.Build, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(KaizenIcons.Wrench, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Spacer(Modifier.width(6.dp))
Text(name, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 12.sp, fontWeight = FontWeight.Medium)
}
@ -184,7 +175,7 @@ fun SourcesBlock(sources: List<SearchSource>, query: String = "") {
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Rounded.Language, null, tint = cs.onSurfaceVariant.copy(alpha = 0.6f), modifier = Modifier.size(13.dp))
Icon(KaizenIcons.Globe, null, tint = cs.onSurfaceVariant.copy(alpha = 0.6f), modifier = Modifier.size(13.dp))
Spacer(Modifier.width(6.dp))
Text(
stringResource(R.string.stream_sources) + " (${sources.size})",
@ -194,7 +185,7 @@ fun SourcesBlock(sources: List<SearchSource>, query: String = "") {
)
Spacer(Modifier.width(4.dp))
Icon(
if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
if (expanded) KaizenIcons.ChevronUp else KaizenIcons.ChevronDown,
null, tint = cs.onSurfaceVariant.copy(alpha = 0.4f), modifier = Modifier.size(12.dp),
)
}

View file

@ -19,10 +19,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Fingerprint
import androidx.compose.material.icons.rounded.Key
import androidx.compose.material.icons.rounded.PhoneAndroid
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -57,6 +53,7 @@ import dev.kaizen.app.net.AppDevice
import dev.kaizen.app.net.ServerConfig
import dev.kaizen.app.ui.shape.KaizenShapes
import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable
fun SecuritySection(
@ -100,7 +97,7 @@ fun SecuritySection(
title = stringResource(R.string.settings_biometric_title),
description = if (biometricNotEnrolled) stringResource(R.string.settings_biometric_not_enrolled)
else stringResource(R.string.settings_biometric_desc),
icon = Icons.Rounded.Fingerprint,
icon = KaizenIcons.Fingerprint,
) {
Switch(
checked = biometricEnabled && biometricAvailable,
@ -119,7 +116,7 @@ fun SecuritySection(
SettingsOptionRow(
title = stringResource(R.string.settings_devices_title),
description = stringResource(R.string.settings_devices_desc),
icon = Icons.Rounded.PhoneAndroid,
icon = KaizenIcons.Smartphone,
) {
if (devicesLoading) {
CircularProgressIndicator(
@ -152,7 +149,7 @@ fun SecuritySection(
SettingsOptionRow(
title = stringResource(R.string.settings_password_title),
description = "",
icon = Icons.Rounded.Key,
icon = KaizenIcons.Key,
) {
PasswordChangeForm(
onSubmit = { current, new_ ->

View file

@ -31,8 +31,6 @@ import dev.kaizen.app.ui.effect.KaizenShadows
import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -57,6 +55,7 @@ import androidx.compose.ui.unit.sp
import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.ui.theme.Oklab
import dev.kaizen.app.ui.theme.themes.KaizenThemeId
import dev.kaizen.app.ui.icon.KaizenIcons
/** Converts polar OKLCH coordinates (Lightness, Chroma, Hue in degrees) to a standard Color */
private fun oklch(l: Float, c: Float, h: Float, alpha: Float = 1.0f): Color {
@ -250,7 +249,7 @@ fun ThemeOptionPill(
if (selected) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Rounded.Check,
imageVector = KaizenIcons.Check,
contentDescription = null,
tint = if (themeId == KaizenThemeId.MONO && isDark.not()) Color.White else Color.Black,
modifier = Modifier.size(16.dp)

View file

@ -22,13 +22,6 @@ import dev.kaizen.app.ui.effect.GlassTiers
import dev.kaizen.app.ui.effect.KaizenShadows
import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.Language
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -47,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalContext
import dev.kaizen.app.chat.MeshBackground
import dev.kaizen.app.ui.icon.KaizenIcons
/**
* Premium, fully responsive, glassmorphic Settings Screen.
@ -95,7 +89,7 @@ fun SettingsScreen(
modifier = Modifier.size(44.dp).clickable { onBack() },
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.settings_back), tint = cs.onBackground, modifier = Modifier.size(20.dp))
Icon(KaizenIcons.ArrowLeft, stringResource(R.string.settings_back), tint = cs.onBackground, modifier = Modifier.size(20.dp))
}
}
@ -107,7 +101,7 @@ fun SettingsScreen(
Text(stringResource(R.string.settings_section_appearance), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp, bottom = 8.dp))
SettingsCard(modifier = Modifier.padding(bottom = 24.dp)) {
SettingsOptionRow(title = stringResource(R.string.settings_accent_title), description = stringResource(R.string.settings_accent_desc), icon = Icons.Rounded.Palette) {
SettingsOptionRow(title = stringResource(R.string.settings_accent_title), description = stringResource(R.string.settings_accent_desc), icon = KaizenIcons.Palette) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
KaizenThemeId.entries.forEach { theme ->
ThemeOptionPill(themeId = theme, selected = selectedTheme == theme, onClick = { viewModel.selectTheme(theme) })
@ -115,7 +109,7 @@ fun SettingsScreen(
}
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_appearance_title), description = stringResource(R.string.settings_appearance_desc), icon = Icons.Rounded.Settings) {
SettingsOptionRow(title = stringResource(R.string.settings_appearance_title), description = stringResource(R.string.settings_appearance_desc), icon = KaizenIcons.Settings) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
AppAppearance.entries.forEach { mode ->
val label = when (mode) {
@ -128,7 +122,7 @@ fun SettingsScreen(
}
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = Icons.Rounded.Language) {
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = KaizenIcons.Globe) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
listOf("system" to R.string.settings_language_system, "de" to R.string.settings_language_de, "en" to R.string.settings_language_en).forEach { (code, labelRes) ->
val ctx = LocalContext.current
@ -141,17 +135,17 @@ fun SettingsScreen(
Text(stringResource(R.string.settings_section_profile), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp, bottom = 8.dp))
SettingsCard(modifier = Modifier.padding(bottom = 24.dp)) {
SettingsOptionRow(title = stringResource(R.string.settings_name_title), description = stringResource(R.string.settings_name_desc), icon = Icons.Rounded.Person) {
SettingsOptionRow(title = stringResource(R.string.settings_name_title), description = stringResource(R.string.settings_name_desc), icon = KaizenIcons.User) {
SettingsInputCapsule(value = userName, onValueChange = { viewModel.updateUserName(it) })
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_email_title), description = userEmail, icon = Icons.Rounded.Settings)
SettingsOptionRow(title = stringResource(R.string.settings_email_title), description = userEmail, icon = KaizenIcons.Settings)
}
Text(stringResource(R.string.settings_section_ai), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp, bottom = 8.dp))
SettingsCard(modifier = Modifier.padding(bottom = 24.dp)) {
SettingsOptionRow(title = stringResource(R.string.settings_ai_title), description = "", icon = Icons.Rounded.AutoAwesome)
SettingsOptionRow(title = stringResource(R.string.settings_ai_title), description = "", icon = KaizenIcons.Sparkles)
}
SecuritySection(viewModel = viewModel, config = config)

View file

@ -0,0 +1,98 @@
package dev.kaizen.app.ui.icon
import androidx.compose.ui.graphics.vector.ImageVector
import com.composables.icons.lucide.*
/**
* Central icon registry for the entire app. Every UI component references icons
* through this object never directly from Material, Lucide, or any other
* library. This gives us:
*
* 1. **One-line swap**: change the backing icon here, every usage updates
* 2. **Library independence**: migrate from Lucide to custom SVGs gradually
* 3. **Semantic naming**: `KaizenIcons.Send` not `Icons.Rounded.Send`
* 4. **Brand layer**: the [Custom] section holds hand-drawn Kaizen originals
* that override the base library for brand-critical touchpoints
*
* Convention: group by function, alphabetize within groups.
* When adding a custom icon, keep the Lucide fallback as a comment.
*/
object KaizenIcons {
// ── Navigation ──────────────────────────────────────────────────────────
val ArrowLeft: ImageVector get() = Lucide.ArrowLeft
val ChevronDown: ImageVector get() = Lucide.ChevronDown
val ChevronUp: ImageVector get() = Lucide.ChevronUp
val Menu: ImageVector get() = Lucide.Menu
val MoreVertical: ImageVector get() = Lucide.EllipsisVertical
val Settings: ImageVector get() = Lucide.Settings
val X: ImageVector get() = Lucide.X
// ── Chat ────────────────────────────────────────────────────────────────
val Mic: ImageVector get() = Lucide.Mic
val Paperclip: ImageVector get() = Lucide.Paperclip
val Plus: ImageVector get() = Lucide.Plus
val Send: ImageVector get() = Lucide.SendHorizontal
val Sparkles: ImageVector get() = Lucide.Sparkles
val Square: ImageVector get() = Lucide.Square
val Zap: ImageVector get() = Lucide.Zap
// ── Modes ───────────────────────────────────────────────────────────────
val Brain: ImageVector get() = Lucide.Brain
val CircleDot: ImageVector get() = Lucide.CircleDot
val Globe: ImageVector get() = Lucide.Globe
val Image: ImageVector get() = Lucide.Image
val Music: ImageVector get() = Lucide.Music
val SlidersHorizontal: ImageVector get() = Lucide.SlidersHorizontal
val Video: ImageVector get() = Lucide.Video
// ── Conversation management ─────────────────────────────────────────────
val Check: ImageVector get() = Lucide.Check
val Copy: ImageVector get() = Lucide.Copy
val Lock: ImageVector get() = Lucide.Lock
val LockOpen: ImageVector get() = Lucide.LockOpen
val Pencil: ImageVector get() = Lucide.Pencil
val Pin: ImageVector get() = Lucide.Pin
val PinOff: ImageVector get() = Lucide.PinOff
val RefreshCw: ImageVector get() = Lucide.RefreshCw
val Star: ImageVector get() = Lucide.Star
val StarOff: ImageVector get() = Lucide.StarOff
val Trash: ImageVector get() = Lucide.Trash2
// ── Files & media ───────────────────────────────────────────────────────
val Camera: ImageVector get() = Lucide.Camera
val Code: ImageVector get() = Lucide.Code
val File: ImageVector get() = Lucide.File
val FileAudio: ImageVector get() = Lucide.FileAudio
val FileText: ImageVector get() = Lucide.FileText
val FileVideo: ImageVector get() = Lucide.FileVideo
val FileImage: ImageVector get() = Lucide.FileImage
// ── Search & reasoning ──────────────────────────────────────────────────
val Search: ImageVector get() = Lucide.Search
val ExternalLink: ImageVector get() = Lucide.ExternalLink
val Wrench: ImageVector get() = Lucide.Wrench
val AlertCircle: ImageVector get() = Lucide.CircleAlert
// ── Settings ────────────────────────────────────────────────────────────
val Fingerprint: ImageVector get() = Lucide.Fingerprint
val Key: ImageVector get() = Lucide.Key
val Languages: ImageVector get() = Lucide.Languages
val LogOut: ImageVector get() = Lucide.LogOut
val Monitor: ImageVector get() = Lucide.Monitor
val Moon: ImageVector get() = Lucide.Moon
val Palette: ImageVector get() = Lucide.Palette
val Smartphone: ImageVector get() = Lucide.Smartphone
val Sun: ImageVector get() = Lucide.Sun
val User: ImageVector get() = Lucide.User
// ── Scroll ──────────────────────────────────────────────────────────────
val ArrowDown: ImageVector get() = Lucide.ArrowDown
val ArrowUp: ImageVector get() = Lucide.ArrowUp
// ── Custom (brand overrides) ────────────────────────────────────────────
// Add hand-drawn SVGs here as ImageVector literals when ready.
// Example:
// val Send: ImageVector get() = KaizenCustomIcons.Send
// The Lucide fallback above is then removed.
}

View file

@ -14,6 +14,7 @@ kotlinxSerialization = "1.7.3"
securityCrypto = "1.1.0-alpha06"
room = "2.8.4"
biometric = "1.4.0-alpha02"
composeIcons = "1.0.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -23,6 +24,7 @@ androidx-security-crypto = { group = "androidx.security", name = "security-crypt
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
compose-icons-lucide = { group = "com.composables", name = "icons-lucide-android", version.ref = "composeIcons" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }