Compare commits

..

No commits in common. "875a319dddcd3f0b204b5cf1ddf8e430375ed2e1" and "1362daee531ed2637f9b57f3bc3e35819f587497" have entirely different histories.

14 changed files with 150 additions and 194 deletions

View file

@ -47,7 +47,6 @@ settings/ SettingsScreen, SettingsViewModel, SettingsComponents, SecurityS
ui/
theme/ Color (P3), Theme (multi-theme), Type, Oklab, Dither
theme/themes/ AccentScheme, AmberTheme, BlauTheme, MonoTheme, AuroraTheme, ThemeRegistry
icon/ KaizenIcons (central registry, Lucide base, custom override layer)
shape/ SquircleShape (superellipse), KaizenShapes (token system)
effect/ GlassSurface (frosted glass), Shadow (multi-layer stack)
motion/ Motion (easing curves, spring specs, duration tokens)
@ -66,7 +65,7 @@ ui/
Full spec: `DESIGN.md`.
**Seven pillars** (load-bearing — if any regress, the design regresses):
**Six 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`.
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`.
@ -74,17 +73,14 @@ 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()`.
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.
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.
**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.
**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).
**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 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.
**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.
**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,7 +46,6 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.compose.icons.lucide)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)

View file

@ -39,6 +39,10 @@ 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
@ -75,12 +79,28 @@ 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
@ -91,7 +111,6 @@ 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) {
@ -180,11 +199,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(KaizenIcons.Brain, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Icon(Icons.Rounded.Psychology, 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(KaizenIcons.ChevronDown, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.ExpandMore, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
}
DropdownMenu(expanded = showReasoningMenu, onDismissRequest = { showReasoningMenu = false }) {
ReasoningPreset.entries.forEach { preset ->
@ -215,11 +234,11 @@ fun ModePillsRow(
.padding(horizontal = 13.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(KaizenIcons.SlidersHorizontal, null, tint = if (anyEnabled) accent.primary else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
Icon(Icons.Rounded.Tune, 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(KaizenIcons.ChevronDown, null, tint = cs.onSurfaceVariant.copy(alpha = 0.5f), modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.ExpandMore, 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)) {
@ -313,7 +332,7 @@ fun TokenBadge(used: Int, contextLength: Int, modifier: Modifier = Modifier) {
Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(KaizenIcons.Zap, null, tint = accent.primary, modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.Bolt, null, tint = accent.primary, modifier = Modifier.size(14.dp))
Spacer(Modifier.width(4.dp))
Text(
"${formatK(used)} / ${formatK(contextLength)}",
@ -397,7 +416,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
MessageActionButton(
icon = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
icon = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) {
haptics.tick()
@ -409,7 +428,7 @@ fun MessageRow(
}
}
onDelete?.let { del ->
MessageActionButton(KaizenIcons.Trash, Color(0xFFEF4444).copy(alpha = 0.6f)) {
MessageActionButton(Icons.Rounded.Delete, Color(0xFFEF4444).copy(alpha = 0.6f)) {
haptics.tick(); del(message.wireId)
}
}
@ -450,7 +469,7 @@ fun MessageRow(
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
MessageActionButton(
icon = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
icon = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
tint = if (copied) Color(0xFF10B981) else cs.onSurfaceVariant.copy(alpha = 0.35f),
) {
haptics.tick()
@ -462,12 +481,12 @@ fun MessageRow(
}
}
onRegenerate?.let { regen ->
MessageActionButton(KaizenIcons.RefreshCw, cs.onSurfaceVariant.copy(alpha = 0.35f)) {
MessageActionButton(Icons.Rounded.Refresh, cs.onSurfaceVariant.copy(alpha = 0.35f)) {
haptics.tick(); regen(message.wireId)
}
}
onDelete?.let { del ->
MessageActionButton(KaizenIcons.Trash, Color(0xFFEF4444).copy(alpha = 0.6f)) {
MessageActionButton(Icons.Rounded.Delete, Color(0xFFEF4444).copy(alpha = 0.6f)) {
haptics.tick(); del(message.wireId)
}
}
@ -556,7 +575,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(KaizenIcons.Plus, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
Icon(Icons.Rounded.Add, 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)
@ -572,7 +591,7 @@ fun ChatInput(
SendButton(enabled = canSend, onClick = onSend)
} else {
Box(Modifier.size(44.dp).clip(KaizenShapes.circle), contentAlignment = Alignment.Center) {
Icon(KaizenIcons.Mic, stringResource(R.string.chat_call), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
Icon(Icons.Rounded.Mic, stringResource(R.string.chat_call), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
}
}
}
@ -592,11 +611,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/") -> KaizenIcons.Image
pf.mimeType.startsWith("video/") -> KaizenIcons.FileVideo
pf.mimeType.startsWith("audio/") -> KaizenIcons.FileAudio
pf.mimeType == "application/pdf" -> KaizenIcons.FileText
else -> KaizenIcons.File
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
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(20.dp))
@ -610,14 +629,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(KaizenIcons.X, stringResource(R.string.chat_error), tint = Color.White, modifier = Modifier.size(18.dp))
Icon(Icons.Rounded.Close, 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(KaizenIcons.X, stringResource(R.string.chat_remove), tint = Color.White, modifier = Modifier.size(12.dp))
Icon(Icons.Rounded.Close, stringResource(R.string.chat_remove), tint = Color.White, modifier = Modifier.size(12.dp))
}
}
}
@ -699,11 +718,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" -> KaizenIcons.FileText
"audio" -> KaizenIcons.FileAudio
"video" -> KaizenIcons.FileVideo
"code" -> KaizenIcons.Code
else -> KaizenIcons.File
"pdf" -> Icons.Rounded.PictureAsPdf
"audio" -> Icons.Rounded.AudioFile
"video" -> Icons.Rounded.VideoFile
"code" -> Icons.Rounded.Code
else -> Icons.AutoMirrored.Rounded.InsertDriveFile
}
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))
@ -727,9 +746,9 @@ fun AttachmentMenu(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.Start,
) {
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)
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)
}
}

View file

@ -1,5 +1,13 @@
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
@ -7,7 +15,6 @@ 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 }
@ -47,21 +54,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, 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),
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),
)
data class ChatMode(val labelRes: Int, val icon: ImageVector)
val chatModes = listOf(
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),
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),
)
@Composable

View file

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

View file

@ -19,6 +19,9 @@ 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
@ -46,7 +49,6 @@ 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
@ -524,7 +526,7 @@ private fun CopyButton(code: String) {
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = if (copied) KaizenIcons.Check else KaizenIcons.Copy,
imageVector = if (copied) Icons.Rounded.Check else Icons.Rounded.ContentCopy,
contentDescription = "Kopieren",
tint = if (copied) Color(0xFF22C55E) else cs.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.size(14.dp),

View file

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

View file

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

View file

@ -17,6 +17,16 @@ 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
@ -40,7 +50,6 @@ 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) {
@ -66,7 +75,7 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(KaizenIcons.Brain, null, tint = accent.primary, modifier = Modifier.size(16.dp))
Icon(Icons.Rounded.Psychology, null, tint = accent.primary, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(8.dp))
Text(
stringResource(R.string.stream_reasoning),
@ -76,7 +85,7 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
modifier = Modifier.weight(1f),
)
Icon(
if (expanded) KaizenIcons.ChevronUp else KaizenIcons.ChevronDown,
if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp),
)
}
@ -125,21 +134,21 @@ fun ToolEventsBlock(tools: List<ToolEvent>, streaming: Boolean = false) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
if (analyzing != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(KaizenIcons.FileText, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.Description, 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(KaizenIcons.Sparkles, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.AutoAwesome, 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") KaizenIcons.AlertCircle else KaizenIcons.Check
val icon = if (tool.type == "error") Icons.Rounded.Error else Icons.Rounded.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))
@ -152,7 +161,7 @@ fun ToolEventsBlock(tools: List<ToolEvent>, streaming: Boolean = false) {
}
inProgress.forEach { name ->
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(KaizenIcons.Wrench, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp))
Icon(Icons.Rounded.Build, 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)
}
@ -175,7 +184,7 @@ fun SourcesBlock(sources: List<SearchSource>, query: String = "") {
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(KaizenIcons.Globe, null, tint = cs.onSurfaceVariant.copy(alpha = 0.6f), modifier = Modifier.size(13.dp))
Icon(Icons.Rounded.Language, 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})",
@ -185,7 +194,7 @@ fun SourcesBlock(sources: List<SearchSource>, query: String = "") {
)
Spacer(Modifier.width(4.dp))
Icon(
if (expanded) KaizenIcons.ChevronUp else KaizenIcons.ChevronDown,
if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
null, tint = cs.onSurfaceVariant.copy(alpha = 0.4f), modifier = Modifier.size(12.dp),
)
}

View file

@ -19,6 +19,10 @@ 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
@ -53,7 +57,6 @@ 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(
@ -97,7 +100,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 = KaizenIcons.Fingerprint,
icon = Icons.Rounded.Fingerprint,
) {
Switch(
checked = biometricEnabled && biometricAvailable,
@ -116,7 +119,7 @@ fun SecuritySection(
SettingsOptionRow(
title = stringResource(R.string.settings_devices_title),
description = stringResource(R.string.settings_devices_desc),
icon = KaizenIcons.Smartphone,
icon = Icons.Rounded.PhoneAndroid,
) {
if (devicesLoading) {
CircularProgressIndicator(
@ -149,7 +152,7 @@ fun SecuritySection(
SettingsOptionRow(
title = stringResource(R.string.settings_password_title),
description = "",
icon = KaizenIcons.Key,
icon = Icons.Rounded.Key,
) {
PasswordChangeForm(
onSubmit = { current, new_ ->

View file

@ -31,6 +31,8 @@ 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
@ -55,7 +57,6 @@ 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 {
@ -249,7 +250,7 @@ fun ThemeOptionPill(
if (selected) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(
imageVector = KaizenIcons.Check,
imageVector = Icons.Rounded.Check,
contentDescription = null,
tint = if (themeId == KaizenThemeId.MONO && isDark.not()) Color.White else Color.Black,
modifier = Modifier.size(16.dp)

View file

@ -22,6 +22,13 @@ 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
@ -40,7 +47,6 @@ 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.
@ -89,7 +95,7 @@ fun SettingsScreen(
modifier = Modifier.size(44.dp).clickable { onBack() },
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(KaizenIcons.ArrowLeft, stringResource(R.string.settings_back), tint = cs.onBackground, modifier = Modifier.size(20.dp))
Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.settings_back), tint = cs.onBackground, modifier = Modifier.size(20.dp))
}
}
@ -101,7 +107,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 = KaizenIcons.Palette) {
SettingsOptionRow(title = stringResource(R.string.settings_accent_title), description = stringResource(R.string.settings_accent_desc), icon = Icons.Rounded.Palette) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
KaizenThemeId.entries.forEach { theme ->
ThemeOptionPill(themeId = theme, selected = selectedTheme == theme, onClick = { viewModel.selectTheme(theme) })
@ -109,7 +115,7 @@ fun SettingsScreen(
}
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_appearance_title), description = stringResource(R.string.settings_appearance_desc), icon = KaizenIcons.Settings) {
SettingsOptionRow(title = stringResource(R.string.settings_appearance_title), description = stringResource(R.string.settings_appearance_desc), icon = Icons.Rounded.Settings) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
AppAppearance.entries.forEach { mode ->
val label = when (mode) {
@ -122,7 +128,7 @@ fun SettingsScreen(
}
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = KaizenIcons.Globe) {
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = Icons.Rounded.Language) {
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
@ -135,17 +141,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 = KaizenIcons.User) {
SettingsOptionRow(title = stringResource(R.string.settings_name_title), description = stringResource(R.string.settings_name_desc), icon = Icons.Rounded.Person) {
SettingsInputCapsule(value = userName, onValueChange = { viewModel.updateUserName(it) })
}
Spacer(Modifier.height(12.dp))
SettingsOptionRow(title = stringResource(R.string.settings_email_title), description = userEmail, icon = KaizenIcons.Settings)
SettingsOptionRow(title = stringResource(R.string.settings_email_title), description = userEmail, icon = Icons.Rounded.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 = KaizenIcons.Sparkles)
SettingsOptionRow(title = stringResource(R.string.settings_ai_title), description = "", icon = Icons.Rounded.AutoAwesome)
}
SecuritySection(viewModel = viewModel, config = config)

View file

@ -1,98 +0,0 @@
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,7 +14,6 @@ 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" }
@ -24,7 +23,6 @@ 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" }