From 68721d8ddfd6dda48973bb721d42aed8954eb956 Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Mon, 22 Jun 2026 14:11:34 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20KaizenIcons=20system=20=E2=80=94=20Luci?= =?UTF-8?q?de=20base,=20full=20Material=20Icons=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/build.gradle.kts | 1 + .../dev/kaizen/app/chat/ChatComponents.kt | 75 ++++++-------- .../java/dev/kaizen/app/chat/ChatModels.kt | 29 +++--- .../java/dev/kaizen/app/chat/ChatScreen.kt | 11 +-- .../main/java/dev/kaizen/app/chat/Markdown.kt | 6 +- .../java/dev/kaizen/app/chat/ModelSheet.kt | 13 ++- .../main/java/dev/kaizen/app/chat/Sidebar.kt | 34 +++---- .../java/dev/kaizen/app/chat/StreamBlocks.kt | 27 ++--- .../kaizen/app/settings/SecuritySection.kt | 11 +-- .../kaizen/app/settings/SettingsComponents.kt | 5 +- .../dev/kaizen/app/settings/SettingsScreen.kt | 22 ++--- .../dev/kaizen/app/ui/icon/KaizenIcons.kt | 98 +++++++++++++++++++ gradle/libs.versions.toml | 2 + 13 files changed, 187 insertions(+), 147 deletions(-) create mode 100644 app/src/main/java/dev/kaizen/app/ui/icon/KaizenIcons.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ea90037..6bbb52f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) 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 5ad3456..c8a1483 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt @@ -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) } } diff --git a/app/src/main/java/dev/kaizen/app/chat/ChatModels.kt b/app/src/main/java/dev/kaizen/app/chat/ChatModels.kt index f988668..6251b46 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatModels.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatModels.kt @@ -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 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 22bc549..5eea00b 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt @@ -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), diff --git a/app/src/main/java/dev/kaizen/app/chat/Markdown.kt b/app/src/main/java/dev/kaizen/app/chat/Markdown.kt index fcbb79f..a4d36b4 100644 --- a/app/src/main/java/dev/kaizen/app/chat/Markdown.kt +++ b/app/src/main/java/dev/kaizen/app/chat/Markdown.kt @@ -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), diff --git a/app/src/main/java/dev/kaizen/app/chat/ModelSheet.kt b/app/src/main/java/dev/kaizen/app/chat/ModelSheet.kt index 14299e3..8619837 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ModelSheet.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ModelSheet.kt @@ -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): 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) diff --git a/app/src/main/java/dev/kaizen/app/chat/Sidebar.kt b/app/src/main/java/dev/kaizen/app/chat/Sidebar.kt index 3cca414..79e7ac0 100644 --- a/app/src/main/java/dev/kaizen/app/chat/Sidebar.kt +++ b/app/src/main/java/dev/kaizen/app/chat/Sidebar.kt @@ -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() } } } } diff --git a/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt b/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt index ffc96b3..d186d59 100644 --- a/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt +++ b/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt @@ -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, 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, 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, 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, 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), ) } diff --git a/app/src/main/java/dev/kaizen/app/settings/SecuritySection.kt b/app/src/main/java/dev/kaizen/app/settings/SecuritySection.kt index 695a9a0..7aa2921 100644 --- a/app/src/main/java/dev/kaizen/app/settings/SecuritySection.kt +++ b/app/src/main/java/dev/kaizen/app/settings/SecuritySection.kt @@ -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_ -> diff --git a/app/src/main/java/dev/kaizen/app/settings/SettingsComponents.kt b/app/src/main/java/dev/kaizen/app/settings/SettingsComponents.kt index ad71181..6c51788 100644 --- a/app/src/main/java/dev/kaizen/app/settings/SettingsComponents.kt +++ b/app/src/main/java/dev/kaizen/app/settings/SettingsComponents.kt @@ -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) diff --git a/app/src/main/java/dev/kaizen/app/settings/SettingsScreen.kt b/app/src/main/java/dev/kaizen/app/settings/SettingsScreen.kt index 8d638f2..5726c2e 100644 --- a/app/src/main/java/dev/kaizen/app/settings/SettingsScreen.kt +++ b/app/src/main/java/dev/kaizen/app/settings/SettingsScreen.kt @@ -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) diff --git a/app/src/main/java/dev/kaizen/app/ui/icon/KaizenIcons.kt b/app/src/main/java/dev/kaizen/app/ui/icon/KaizenIcons.kt new file mode 100644 index 0000000..32b0106 --- /dev/null +++ b/app/src/main/java/dev/kaizen/app/ui/icon/KaizenIcons.kt @@ -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. +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94df565..268ae59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }