Compare commits

...

2 commits

Author SHA1 Message Date
Bruno Deanoz
875a319ddd docs: document KaizenIcons as 7th design system pillar
Icon system is now the 7th pillar alongside P3 color, squircle shapes,
GlassSurface, multi-layer shadows, 4-theme system, and sensor motion.
Documents the Lucide base, custom override architecture, semantic
grouping, and how to add hand-drawn SVG overrides. Also documents the
unified top bar redesign (merged sidebar + model pill).
2026-06-22 14:13:10 +02:00
Bruno Deanoz
68721d8ddf 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
2026-06-22 14:11:34 +02:00
14 changed files with 194 additions and 150 deletions

View file

@ -47,6 +47,7 @@ settings/ SettingsScreen, SettingsViewModel, SettingsComponents, SecurityS
ui/ ui/
theme/ Color (P3), Theme (multi-theme), Type, Oklab, Dither theme/ Color (P3), Theme (multi-theme), Type, Oklab, Dither
theme/themes/ AccentScheme, AmberTheme, BlauTheme, MonoTheme, AuroraTheme, ThemeRegistry theme/themes/ AccentScheme, AmberTheme, BlauTheme, MonoTheme, AuroraTheme, ThemeRegistry
icon/ KaizenIcons (central registry, Lucide base, custom override layer)
shape/ SquircleShape (superellipse), KaizenShapes (token system) shape/ SquircleShape (superellipse), KaizenShapes (token system)
effect/ GlassSurface (frosted glass), Shadow (multi-layer stack) effect/ GlassSurface (frosted glass), Shadow (multi-layer stack)
motion/ Motion (easing curves, spring specs, duration tokens) motion/ Motion (easing curves, spring specs, duration tokens)
@ -65,7 +66,7 @@ ui/
Full spec: `DESIGN.md`. Full spec: `DESIGN.md`.
**Six pillars** (load-bearing — if any regress, the design regresses): **Seven pillars** (load-bearing — if any regress, the design regresses):
1. **P3 color pipeline** — all tokens in DisplayP3 color space (`Color.kt`). `window.colorMode = COLOR_MODE_HDR` (API 34+) / `COLOR_MODE_WIDE_COLOR_GAMUT` (API 26+) in `MainActivity`. No `Color(0xFF…)` for brand colors outside `Color.kt`. 1. **P3 color pipeline** — all tokens in DisplayP3 color space (`Color.kt`). `window.colorMode = COLOR_MODE_HDR` (API 34+) / `COLOR_MODE_WIDE_COLOR_GAMUT` (API 26+) in `MainActivity`. No `Color(0xFF…)` for brand colors outside `Color.kt`.
2. **Squircle shapes** — iOS-style superellipse with G2 continuity (`ui/shape/Squircle.kt`). Token system `KaizenShapes.{xs,sm,md,lg,xl,pill,circle}`. Replaces all `RoundedCornerShape`. 2. **Squircle shapes** — iOS-style superellipse with G2 continuity (`ui/shape/Squircle.kt`). Token system `KaizenShapes.{xs,sm,md,lg,xl,pill,circle}`. Replaces all `RoundedCornerShape`.
@ -73,14 +74,17 @@ Full spec: `DESIGN.md`.
4. **Multi-layer shadows**`ShadowStack` with 24 overlapping layers (`KaizenShadows.level0..4`). Dark mode multiplies alphas ×1.8. Replaces all `Modifier.shadow()`. 4. **Multi-layer shadows**`ShadowStack` with 24 overlapping layers (`KaizenShadows.level0..4`). Dark mode multiplies alphas ×1.8. Replaces all `Modifier.shadow()`.
5. **4-theme system** — Amber/Blau/Mono/Aurora. Each theme = `KaizenAccentScheme` (primary + 4 blob colors). Neutrals are theme-independent. `LocalKaizenAccent` CompositionLocal. Theme selection wired end-to-end: `SettingsViewModel.selectedTheme``MainActivity``KaizenTheme(themeId=...)`. No server sync (web stores in localStorage, app stores locally). Appearance (Light/Dark/System) also wired end-to-end. 5. **4-theme system** — Amber/Blau/Mono/Aurora. Each theme = `KaizenAccentScheme` (primary + 4 blob colors). Neutrals are theme-independent. `LocalKaizenAccent` CompositionLocal. Theme selection wired end-to-end: `SettingsViewModel.selectedTheme``MainActivity``KaizenTheme(themeId=...)`. No server sync (web stores in localStorage, app stores locally). Appearance (Light/Dark/System) also wired end-to-end.
6. **Sensor-reactive motion**`rememberTiltState()` provides smoothed tilt `Offset(-1..1)`. Uses `TYPE_GAME_ROTATION_VECTOR` (fused, no drift). Auto-disables on battery saver, reduced motion, background, extreme tilt. 6. **Sensor-reactive motion**`rememberTiltState()` provides smoothed tilt `Offset(-1..1)`. Uses `TYPE_GAME_ROTATION_VECTOR` (fused, no drift). Auto-disables on battery saver, reduced motion, background, extreme tilt.
7. **KaizenIcons** (`ui/icon/KaizenIcons.kt`) — centralized icon registry, same architecture as Google's Luminous Symbols. Lucide (1.5px stroke, same library as web frontend) as base, with a custom override layer for brand-critical icons. Every UI component references `KaizenIcons.Send`, `KaizenIcons.Lock`, etc. — never a library directly. Zero Material Icon imports remain. To add a custom icon: create an `ImageVector` literal (hand-drawn SVG converted to Compose path data) and swap the getter in `KaizenIcons.kt` — all usages update instantly, no call-site changes. Icons grouped semantically: Navigation, Chat, Modes, Conversation, Files, Search, Settings, Scroll. `com.composables:icons-lucide-android:1.0.0`.
**Motion tokens** (`ui/motion/Motion.kt`): `EaseSpring` (overshoot), `EaseSmooth` (expo-out), `EaseEmphasized` (M3), `SpringSnappy/Default/Gentle`. All durations centralised in `Durations` object. **Motion tokens** (`ui/motion/Motion.kt`): `EaseSpring` (overshoot), `EaseSmooth` (expo-out), `EaseEmphasized` (M3), `SpringSnappy/Default/Gentle`. All durations centralised in `Durations` object.
**Migration status:** All UI components migrated to the design system. Sidebar, ChatInput, ChatScreen, LoginScreen, ModelSheet, SettingsCard, SettingsScreen use `GlassSurface` + `KaizenShapes` + `kaizenShadow`. Only Markdown code-block shapes remain as `RoundedCornerShape` (intentionally rectangular for code). **Migration status:** All UI components migrated to the design system. Sidebar, ChatInput, ChatScreen, LoginScreen, ModelSheet, SettingsCard, SettingsScreen use `GlassSurface` + `KaizenShapes` + `kaizenShadow` + `KaizenIcons`. Only Markdown code-block shapes remain as `RoundedCornerShape` (intentionally rectangular for code). All icons migrated from Material Icons Rounded to KaizenIcons/Lucide — zero `Icons.Rounded.*` imports remain.
**Theme switching fully wired:** All 7 files that previously imported hardcoded `Amber`/`AmberDeep`/`OnAmber`/`BlobX` now read from `LocalKaizenAccent.current`. Changing theme in Settings immediately updates: message bubbles, send button, orb, mesh background blobs, sidebar highlights, model sheet, stream blocks, login screen, settings toggles. No hardcoded amber imports remain outside `Color.kt` and theme definitions. **Theme switching fully wired:** All 7 files that previously imported hardcoded `Amber`/`AmberDeep`/`OnAmber`/`BlobX` now read from `LocalKaizenAccent.current`. Changing theme in Settings immediately updates: message bubbles, send button, orb, mesh background blobs, sidebar highlights, model sheet, stream blocks, login screen, settings toggles. No hardcoded amber imports remain outside `Color.kt` and theme definitions.
**Floating bar scrims:** Top bar (hamburger + model pill) and bottom dock (mode pills + input) have vertical gradient scrims (`MaterialTheme.colorScheme.background` → transparent) to prevent message content from bleeding through. Model pill opacity increased to 0.92. **Floating bar scrims:** Top bar and bottom dock have vertical gradient scrims (`MaterialTheme.colorScheme.background` → transparent) to prevent message content from bleeding through.
**Unified top bar:** Sidebar trigger (menu icon) and model name merged into a single `GlassSurface` pill (38dp, `KaizenShapes.pill`). Menu icon (17dp, muted) → 3dp dot divider → model name (13sp Medium, 180dp max with ellipsis). No chevron — pill is self-evidently tappable. Matches the TokenBadge glass language on the right.
**KaizenOrb** (`chat/Background.kt`) uses `rememberTiltState()` from `ui/sensor/` instead of inline sensor code. Breath animation uses `Durations.ORB_BREATH` / `ORB_BREATH_STREAMING` from `ui/motion/`. **KaizenOrb** (`chat/Background.kt`) uses `rememberTiltState()` from `ui/sensor/` instead of inline sensor code. Breath animation uses `Durations.ORB_BREATH` / `ORB_BREATH_STREAMING` from `ui/motion/`.

View file

@ -46,6 +46,7 @@ dependencies {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.compose.icons.lucide)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) 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.rememberScrollState
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll 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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -79,28 +75,12 @@ import java.time.format.DateTimeFormatter
import java.util.Locale import java.util.Locale
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.compose.foundation.Image 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.mutableStateOf
import androidx.compose.runtime.setValue 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 androidx.compose.ui.platform.LocalClipboard
import dev.kaizen.app.haptics.rememberHaptics import dev.kaizen.app.haptics.rememberHaptics
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch 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.runtime.setValue
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
@ -111,6 +91,7 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable @Composable
fun KaizenHeader(modifier: Modifier = Modifier) { fun KaizenHeader(modifier: Modifier = Modifier) {
@ -199,11 +180,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp), .padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically, 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)) Spacer(Modifier.width(6.dp))
Text(stringResource(reasoningPreset.label), color = cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium) Text(stringResource(reasoningPreset.label), color = cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.width(4.dp)) 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 }) { DropdownMenu(expanded = showReasoningMenu, onDismissRequest = { showReasoningMenu = false }) {
ReasoningPreset.entries.forEach { preset -> ReasoningPreset.entries.forEach { preset ->
@ -234,11 +215,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp), .padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically, 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)) 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) 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)) 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 }) { DropdownMenu(expanded = showSamplingPopup, onDismissRequest = { showSamplingPopup = false }) {
Column(Modifier.padding(horizontal = 16.dp, vertical = 8.dp).width(240.dp)) { 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), Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically, 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)) Spacer(Modifier.width(4.dp))
Text( Text(
"${formatK(used)} / ${formatK(contextLength)}", "${formatK(used)} / ${formatK(contextLength)}",
@ -416,7 +397,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp), horizontalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
MessageActionButton( 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), tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) { ) {
haptics.tick() haptics.tick()
@ -428,7 +409,7 @@ fun MessageRow(
} }
} }
onDelete?.let { del -> 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) haptics.tick(); del(message.wireId)
} }
} }
@ -469,7 +450,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp), horizontalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
MessageActionButton( 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), tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) { ) {
haptics.tick() haptics.tick()
@ -481,12 +462,12 @@ fun MessageRow(
} }
} }
onRegenerate?.let { regen -> 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) haptics.tick(); regen(message.wireId)
} }
} }
onDelete?.let { del -> 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) 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) { 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) { 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) { 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) 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) SendButton(enabled = canSend, onClick = onSend)
} else { } else {
Box(Modifier.size(44.dp).clip(KaizenShapes.circle), contentAlignment = Alignment.Center) { 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)) NetworkImage(url = pf.uploaded.url, contentDescription = pf.name, modifier = Modifier.fillMaxSize().clip(KaizenShapes.xs))
} else { } else {
val icon = when { val icon = when {
pf.mimeType.startsWith("image/") -> Icons.Rounded.Image pf.mimeType.startsWith("image/") -> KaizenIcons.Image
pf.mimeType.startsWith("video/") -> Icons.Rounded.VideoFile pf.mimeType.startsWith("video/") -> KaizenIcons.FileVideo
pf.mimeType.startsWith("audio/") -> Icons.Rounded.AudioFile pf.mimeType.startsWith("audio/") -> KaizenIcons.FileAudio
pf.mimeType == "application/pdf" -> Icons.Rounded.PictureAsPdf pf.mimeType == "application/pdf" -> KaizenIcons.FileText
else -> Icons.AutoMirrored.Rounded.InsertDriveFile else -> KaizenIcons.File
} }
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(20.dp)) 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) { if (pf.error != null) {
Box(Modifier.fillMaxSize().background(Color(0xFFDC2626).copy(alpha = 0.3f)), contentAlignment = Alignment.Center) { 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( Box(
Modifier.align(Alignment.TopEnd).padding(2.dp).size(18.dp).clip(KaizenShapes.circle).background(Color.Black.copy(alpha = 0.5f)).clickable(onClick = onRemove), 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, 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 isDark = isSystemInDarkTheme()
val bg = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f) val bg = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
val icon = when (attachment.kind) { val icon = when (attachment.kind) {
"pdf" -> Icons.Rounded.PictureAsPdf "pdf" -> KaizenIcons.FileText
"audio" -> Icons.Rounded.AudioFile "audio" -> KaizenIcons.FileAudio
"video" -> Icons.Rounded.VideoFile "video" -> KaizenIcons.FileVideo
"code" -> Icons.Rounded.Code "code" -> KaizenIcons.Code
else -> Icons.AutoMirrored.Rounded.InsertDriveFile else -> KaizenIcons.File
} }
Row(Modifier.clip(KaizenShapes.xs).background(bg).padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically) { 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)) Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
@ -746,9 +727,9 @@ fun AttachmentMenu(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
) { ) {
AttachPill(Icons.Rounded.CameraAlt, stringResource(R.string.attach_camera), accent.primary, isDark, onCamera) AttachPill(KaizenIcons.Camera, stringResource(R.string.attach_camera), accent.primary, isDark, onCamera)
AttachPill(Icons.Rounded.Image, stringResource(R.string.attach_gallery), Color(0xFF6366F1), isDark, onGallery) AttachPill(KaizenIcons.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.File, stringResource(R.string.attach_file), Color(0xFF10B981), isDark, onFile)
} }
} }

View file

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

View file

@ -29,10 +29,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import dev.kaizen.app.ui.icon.KaizenIcons
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -665,7 +662,7 @@ fun ChatScreen(
.clickable { scope.launch { listState.animateScrollToItem(0) } }, .clickable { scope.launch { listState.animateScrollToItem(0) } },
) { ) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 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) { } else if (!atBottom && canScrollDown) {
@ -681,7 +678,7 @@ fun ChatScreen(
.clickable { scope.launch { listState.animateScrollToItem(messages.size - 1) } }, .clickable { scope.launch { listState.animateScrollToItem(messages.size - 1) } },
) { ) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 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, contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
imageVector = Icons.Rounded.Menu, imageVector = KaizenIcons.Menu,
contentDescription = context.getString(R.string.chat_open_menu), contentDescription = context.getString(R.string.chat_open_menu),
tint = MaterialTheme.colorScheme.onSurfaceVariant, tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(17.dp), 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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import dev.kaizen.app.ui.shape.KaizenShapes 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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -49,6 +46,7 @@ import androidx.compose.ui.unit.sp
import dev.kaizen.app.haptics.rememberHaptics import dev.kaizen.app.haptics.rememberHaptics
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import dev.kaizen.app.ui.icon.KaizenIcons
private sealed interface MdBlock { private sealed interface MdBlock {
data class Paragraph(val text: String) : MdBlock data class Paragraph(val text: String) : MdBlock
@ -526,7 +524,7 @@ private fun CopyButton(code: String) {
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
imageVector = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy, imageVector = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
contentDescription = "Kopieren", contentDescription = "Kopieren",
tint = if (copied) Color(0xFF22C55E) else cs.onSurfaceVariant.copy(alpha = 0.5f), tint = if (copied) Color(0xFF22C55E) else cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(14.dp), modifier = Modifier.size(14.dp),

View file

@ -8,8 +8,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import dev.kaizen.app.ui.shape.KaizenShapes import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.text.BasicTextField 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -25,6 +23,7 @@ import dev.kaizen.app.R
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import dev.kaizen.app.ui.theme.LocalKaizenAccent import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.haptics.rememberHaptics 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. */ /** Human label + composite-id resolver for the top pill — falls back to a de-prefixed id. */
fun modelDisplayName(modelId: String, models: List<KaizenModel>): String = fun modelDisplayName(modelId: String, models: List<KaizenModel>): String =
@ -52,7 +51,7 @@ fun ModelPill(label: String, onClick: () -> Unit, modifier: Modifier = Modifier)
maxLines = 1, maxLines = 1,
) )
Icon( Icon(
Icons.Rounded.ExpandMore, KaizenIcons.ChevronDown,
contentDescription = null, contentDescription = null,
tint = cs.onSurfaceVariant, tint = cs.onSurfaceVariant,
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
@ -139,7 +138,7 @@ fun ModelSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
Icons.Rounded.Search, KaizenIcons.Search,
contentDescription = null, contentDescription = null,
tint = cs.onSurfaceVariant.copy(alpha = 0.6f), tint = cs.onSurfaceVariant.copy(alpha = 0.6f),
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
@ -167,7 +166,7 @@ fun ModelSheet(
} }
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
Icon( Icon(
Icons.Rounded.Close, KaizenIcons.X,
contentDescription = stringResource(R.string.model_search_clear), contentDescription = stringResource(R.string.model_search_clear),
tint = cs.onSurfaceVariant.copy(alpha = 0.6f), tint = cs.onSurfaceVariant.copy(alpha = 0.6f),
modifier = Modifier modifier = Modifier
@ -294,7 +293,7 @@ private fun ModelRow(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( 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), contentDescription = stringResource(R.string.model_favorite),
tint = if (favorite) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.35f), tint = if (favorite) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.35f),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@ -304,7 +303,7 @@ private fun ModelRow(
if (selected) { if (selected) {
Spacer(Modifier.size(4.dp)) Spacer(Modifier.size(4.dp))
Icon( Icon(
Icons.Rounded.Check, KaizenIcons.Check,
contentDescription = stringResource(R.string.model_selected), contentDescription = stringResource(R.string.model_selected),
tint = accent.primary, tint = accent.primary,
modifier = Modifier.size(20.dp) 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.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions 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.material3.AlertDialog
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -70,6 +59,7 @@ import java.time.LocalDate
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Locale 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 data class DateLabels(val today: String, val yesterday: String, val earlier: String, val pinned: String)
@ -156,7 +146,7 @@ fun KaizenSidebar(
.clickable { onNewChat() }, .clickable { onNewChat() },
contentAlignment = Alignment.Center 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), .padding(horizontal = 14.dp, vertical = 9.dp),
verticalAlignment = Alignment.CenterVertically 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)) Spacer(Modifier.width(8.dp))
Text(stringResource(R.string.chat_search), color = cs.onSurfaceVariant.copy(alpha = 0.45f), fontSize = 13.sp) 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), modifier = Modifier.weight(1f),
) )
Icon( Icon(
Icons.Rounded.Settings, KaizenIcons.Settings,
stringResource(R.string.sidebar_settings), stringResource(R.string.sidebar_settings),
tint = cs.onSurfaceVariant.copy(alpha = 0.5f), tint = cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
@ -275,7 +265,7 @@ fun KaizenSidebar(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
Icons.AutoMirrored.Rounded.Logout, KaizenIcons.LogOut,
stringResource(R.string.sidebar_logout), stringResource(R.string.sidebar_logout),
tint = cs.onSurfaceVariant.copy(alpha = 0.5f), tint = cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
@ -389,7 +379,7 @@ private fun HistoryItemRow(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (item.pinned) { 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)) Spacer(Modifier.width(6.dp))
} }
Text( Text(
@ -403,7 +393,7 @@ private fun HistoryItemRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
if (item.locked) { 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)) Spacer(Modifier.width(2.dp))
} }
Box( Box(
@ -414,7 +404,7 @@ private fun HistoryItemRow(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
Icons.Rounded.MoreVert, KaizenIcons.MoreVertical,
stringResource(R.string.sidebar_more), stringResource(R.string.sidebar_more),
tint = cs.onSurfaceVariant.copy(alpha = 0.35f), tint = cs.onSurfaceVariant.copy(alpha = 0.35f),
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
@ -430,15 +420,15 @@ private fun HistoryItemRow(
shape = KaizenShapes.md, shape = KaizenShapes.md,
) { ) {
if (!item.locked) { 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( 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), stringResource(if (item.locked) R.string.sidebar_unlock else R.string.sidebar_lock),
cs.onBackground, cs.onBackground,
) { showMenu = false; onToggleLock() } ) { 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.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -50,6 +40,7 @@ import dev.kaizen.app.ui.shape.KaizenShapes
import dev.kaizen.app.ui.theme.LocalKaizenAccent import dev.kaizen.app.ui.theme.LocalKaizenAccent
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable @Composable
fun ReasoningBlock(reasoning: String, streaming: Boolean = false) { 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), .padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically, 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)) Spacer(Modifier.width(8.dp))
Text( Text(
stringResource(R.string.stream_reasoning), stringResource(R.string.stream_reasoning),
@ -85,7 +76,7 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
Icon( 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), 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)) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
if (analyzing != null) { if (analyzing != null) {
Row(verticalAlignment = Alignment.CenterVertically) { 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)) Spacer(Modifier.width(6.dp))
Text(stringResource(R.string.stream_tool_analyzing, analyzing!!), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp) 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()) { if (thinking && analyzing == null && completed.isEmpty() && inProgress.isEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) { 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)) Spacer(Modifier.width(6.dp))
Text(stringResource(R.string.stream_tool_thinking), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp) Text(stringResource(R.string.stream_tool_thinking), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp)
} }
} }
completed.forEach { tool -> completed.forEach { tool ->
Row(verticalAlignment = Alignment.CenterVertically) { 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) val tint = if (tool.type == "error") Color(0xFFEF4444) else Color(0xFF10B981)
Icon(icon, null, tint = tint, modifier = Modifier.size(14.dp)) Icon(icon, null, tint = tint, modifier = Modifier.size(14.dp))
Spacer(Modifier.width(6.dp)) Spacer(Modifier.width(6.dp))
@ -161,7 +152,7 @@ fun ToolEventsBlock(tools: List<ToolEvent>, streaming: Boolean = false) {
} }
inProgress.forEach { name -> inProgress.forEach { name ->
Row(verticalAlignment = Alignment.CenterVertically) { 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)) Spacer(Modifier.width(6.dp))
Text(name, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 12.sp, fontWeight = FontWeight.Medium) 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), .padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically, 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)) Spacer(Modifier.width(6.dp))
Text( Text(
stringResource(R.string.stream_sources) + " (${sources.size})", stringResource(R.string.stream_sources) + " (${sources.size})",
@ -194,7 +185,7 @@ fun SourcesBlock(sources: List<SearchSource>, query: String = "") {
) )
Spacer(Modifier.width(4.dp)) Spacer(Modifier.width(4.dp))
Icon( 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), 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.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions 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.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme 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.net.ServerConfig
import dev.kaizen.app.ui.shape.KaizenShapes import dev.kaizen.app.ui.shape.KaizenShapes
import dev.kaizen.app.ui.theme.LocalKaizenAccent import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.ui.icon.KaizenIcons
@Composable @Composable
fun SecuritySection( fun SecuritySection(
@ -100,7 +97,7 @@ fun SecuritySection(
title = stringResource(R.string.settings_biometric_title), title = stringResource(R.string.settings_biometric_title),
description = if (biometricNotEnrolled) stringResource(R.string.settings_biometric_not_enrolled) description = if (biometricNotEnrolled) stringResource(R.string.settings_biometric_not_enrolled)
else stringResource(R.string.settings_biometric_desc), else stringResource(R.string.settings_biometric_desc),
icon = Icons.Rounded.Fingerprint, icon = KaizenIcons.Fingerprint,
) { ) {
Switch( Switch(
checked = biometricEnabled && biometricAvailable, checked = biometricEnabled && biometricAvailable,
@ -119,7 +116,7 @@ fun SecuritySection(
SettingsOptionRow( SettingsOptionRow(
title = stringResource(R.string.settings_devices_title), title = stringResource(R.string.settings_devices_title),
description = stringResource(R.string.settings_devices_desc), description = stringResource(R.string.settings_devices_desc),
icon = Icons.Rounded.PhoneAndroid, icon = KaizenIcons.Smartphone,
) { ) {
if (devicesLoading) { if (devicesLoading) {
CircularProgressIndicator( CircularProgressIndicator(
@ -152,7 +149,7 @@ fun SecuritySection(
SettingsOptionRow( SettingsOptionRow(
title = stringResource(R.string.settings_password_title), title = stringResource(R.string.settings_password_title),
description = "", description = "",
icon = Icons.Rounded.Key, icon = KaizenIcons.Key,
) { ) {
PasswordChangeForm( PasswordChangeForm(
onSubmit = { current, new_ -> onSubmit = { current, new_ ->

View file

@ -31,8 +31,6 @@ import dev.kaizen.app.ui.effect.KaizenShadows
import dev.kaizen.app.ui.shape.KaizenShapes import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll 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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text 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.LocalKaizenAccent
import dev.kaizen.app.ui.theme.Oklab import dev.kaizen.app.ui.theme.Oklab
import dev.kaizen.app.ui.theme.themes.KaizenThemeId 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 */ /** 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 { private fun oklch(l: Float, c: Float, h: Float, alpha: Float = 1.0f): Color {
@ -250,7 +249,7 @@ fun ThemeOptionPill(
if (selected) { if (selected) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon( Icon(
imageVector = Icons.Rounded.Check, imageVector = KaizenIcons.Check,
contentDescription = null, contentDescription = null,
tint = if (themeId == KaizenThemeId.MONO && isDark.not()) Color.White else Color.Black, tint = if (themeId == KaizenThemeId.MONO && isDark.not()) Color.White else Color.Black,
modifier = Modifier.size(16.dp) 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.effect.KaizenShadows
import dev.kaizen.app.ui.shape.KaizenShapes import dev.kaizen.app.ui.shape.KaizenShapes
import androidx.compose.foundation.verticalScroll 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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text 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.unit.sp
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import dev.kaizen.app.chat.MeshBackground import dev.kaizen.app.chat.MeshBackground
import dev.kaizen.app.ui.icon.KaizenIcons
/** /**
* Premium, fully responsive, glassmorphic Settings Screen. * Premium, fully responsive, glassmorphic Settings Screen.
@ -95,7 +89,7 @@ fun SettingsScreen(
modifier = Modifier.size(44.dp).clickable { onBack() }, modifier = Modifier.size(44.dp).clickable { onBack() },
) { ) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 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)) 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)) { 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) { Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
KaizenThemeId.entries.forEach { theme -> KaizenThemeId.entries.forEach { theme ->
ThemeOptionPill(themeId = theme, selected = selectedTheme == theme, onClick = { viewModel.selectTheme(theme) }) ThemeOptionPill(themeId = theme, selected = selectedTheme == theme, onClick = { viewModel.selectTheme(theme) })
@ -115,7 +109,7 @@ fun SettingsScreen(
} }
} }
Spacer(Modifier.height(12.dp)) 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) { Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
AppAppearance.entries.forEach { mode -> AppAppearance.entries.forEach { mode ->
val label = when (mode) { val label = when (mode) {
@ -128,7 +122,7 @@ fun SettingsScreen(
} }
} }
Spacer(Modifier.height(12.dp)) 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) { 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) -> 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 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)) 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)) { 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) }) SettingsInputCapsule(value = userName, onValueChange = { viewModel.updateUserName(it) })
} }
Spacer(Modifier.height(12.dp)) 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)) 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)) { 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) 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" securityCrypto = "1.1.0-alpha06"
room = "2.8.4" room = "2.8.4"
biometric = "1.4.0-alpha02" biometric = "1.4.0-alpha02"
composeIcons = "1.0.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", 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" } 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" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }