1package soy.iko.opencode.ui.theme
4import androidx.compose.foundation.isSystemInDarkTheme
5import androidx.compose.material3.MaterialTheme
6import androidx.compose.material3.darkColorScheme
7import androidx.compose.material3.dynamicDarkColorScheme
8import androidx.compose.material3.dynamicLightColorScheme
9import androidx.compose.material3.lightColorScheme
10import androidx.compose.runtime.Composable
11import androidx.compose.runtime.SideEffect
12import androidx.compose.ui.graphics.Color
13import androidx.compose.ui.graphics.luminance
14import androidx.compose.ui.platform.LocalContext
15import androidx.compose.ui.platform.LocalView
16import androidx.core.view.WindowCompat
17import soy.iko.opencode.util.findActivity
20 * Tokyo Night — a dark, muted blue/purple palette. Every Material 3 color role is set
21 * explicitly (not just primary/secondary/tertiary) so components that read `error`,
22 * `primaryContainer`, `surfaceVariant`, `outline`, etc. match the brand instead of
23 * falling back to stock M3 defaults that clash with it.
25 * Source palette (Tokyo Night):
26 * bg #1A1B26 fg #C0CAF5 blue #7AA2F7 cyan #7DCFFF
27 * green #9ECE6A magenta #BB9AF7 red #F7768E yellow #E0AF68
28 * comment #565F89 gutter #414868
30private val DarkColors = darkColorScheme(
31 primary = Color(0xFF7AA2F7),
32 onPrimary = Color(0xFF112036),
33 primaryContainer = Color(0xFF2A3A5C),
34 onPrimaryContainer = Color(0xFFD6E2FF),
35 secondary = Color(0xFF9ECE6A),
36 onSecondary = Color(0xFF13260A),
37 secondaryContainer = Color(0xFF2B3B1F),
38 onSecondaryContainer = Color(0xFFD2F0A0),
39 tertiary = Color(0xFFBB9AF7),
40 onTertiary = Color(0xFF221036),
41 tertiaryContainer = Color(0xFF3A2A55),
42 onTertiaryContainer = Color(0xFFE6D6FF),
43 error = Color(0xFFF7768E),
44 onError = Color(0xFF370610),
45 errorContainer = Color(0xFF5C1A24),
46 onErrorContainer = Color(0xFFFFD9DE),
47 background = Color(0xFF1A1B26),
48 onBackground = Color(0xFFC0CAF5),
49 surface = Color(0xFF1F2335),
50 onSurface = Color(0xFFC0CAF5),
51 surfaceVariant = Color(0xFF2D3247),
52 onSurfaceVariant = Color(0xFF9AA5CE),
53 surfaceTint = Color(0xFF7AA2F7),
54 inverseSurface = Color(0xFFC0CAF5),
55 inverseOnSurface = Color(0xFF1A1B26),
56 outline = Color(0xFF565F89),
57 outlineVariant = Color(0xFF414868),
58 scrim = Color(0xFF000000),
62 * Tokyo Night Light — the day-mode counterpart. Pairs the same hue family with a soft
63 * off-white background so the brand identity survives a light setting instead of
64 * snapping to stock M3 purple-grey.
66private val LightColors = lightColorScheme(
67 primary = Color(0xFF34548A),
68 onPrimary = Color(0xFFFFFFFF),
69 primaryContainer = Color(0xFFD6E2FF),
70 onPrimaryContainer = Color(0xFF001A41),
71 secondary = Color(0xFF485E30),
72 onSecondary = Color(0xFFFFFFFF),
73 secondaryContainer = Color(0xFFC8F0A0),
74 onSecondaryContainer = Color(0xFF0E2000),
75 tertiary = Color(0xFF5A4A78),
76 onTertiary = Color(0xFFFFFFFF),
77 tertiaryContainer = Color(0xFFE6D6FF),
78 onTertiaryContainer = Color(0xFF180038),
79 error = Color(0xFF9B1C2E),
80 onError = Color(0xFFFFFFFF),
81 errorContainer = Color(0xFFFFD9DE),
82 onErrorContainer = Color(0xFF410008),
83 background = Color(0xFFF6F7FB),
84 onBackground = Color(0xFF1A1B26),
85 surface = Color(0xFFF1F3FA),
86 onSurface = Color(0xFF1A1B26),
87 surfaceVariant = Color(0xFFE0E4F0),
88 onSurfaceVariant = Color(0xFF444B6E),
89 surfaceTint = Color(0xFF34548A),
90 inverseSurface = Color(0xFF2D3247),
91 inverseOnSurface = Color(0xFFEEF0FB),
92 outline = Color(0xFF747DAA),
93 outlineVariant = Color(0xFFC3C8DA),
94 scrim = Color(0xFF000000),
98 * AMOLED — the Tokyo Night dark palette with pure-black backgrounds (#000000) so OLED
99 * displays power off pixels in large black areas, extending battery life. The accent
100 * colours stay the same so the brand identity is preserved; only the backgrounds and
101 * surfaces drop to true black.
103 * Every Material 3 surface role is overridden — not just `background`/`surface` — because
104 * stock M3 components (Card, ModalBottomSheet, AlertDialog, MenuItem) read the
105 * `surfaceContainer*` / `surfaceBright` / `surfaceDim` tiers internally. Leaving those at
106 * the Tokyo-Night-dark values would leak non-black dialogs and sheets, undercutting the
107 * OLED power-off promise. The container tiers step up from pure black so elevated surfaces
108 * (cards over the list, dialogs) still read as a distinct layer without powering pixels on.
110private val AmoledColors = DarkColors.copy(
111 background = Color(0xFF000000),
112 surface = Color(0xFF000000),
113 surfaceVariant = Color(0xFF1A1F2E),
114 surfaceDim = Color(0xFF000000),
115 surfaceBright = Color(0xFF1A1F2E),
116 surfaceContainerLowest = Color(0xFF000000),
117 surfaceContainerLow = Color(0xFF000000),
118 surfaceContainer = Color(0xFF0A0B10),
119 surfaceContainerHigh = Color(0xFF12141C),
120 surfaceContainerHighest = Color(0xFF1A1F2E),
121 inverseOnSurface = Color(0xFF000000),
126 darkTheme: Boolean = isSystemInDarkTheme(),
127 dynamicColor: Boolean = false,
128 amoled: Boolean = false,
129 content: @Composable () -> Unit,
131 val context = LocalContext.current
132 val colorScheme = when {
133 // AMOLED takes precedence over dynamic color: a user who explicitly chose pure-black
134 // surfaces wants OLED-black even with Material You on, rather than silently getting the
135 // wallpaper-derived palette (which left the AMOLED choice appearing to do nothing).
136 darkTheme && amoled -> AmoledColors
137 dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
138 if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
139 darkTheme -> DarkColors
142 val view = LocalView.current
143 if (!view.isInEditMode) {
145 val window = view.context.findActivity()?.window ?: return@SideEffect
146 val controller = WindowCompat.getInsetsController(window, view)
147 controller.isAppearanceLightStatusBars = !darkTheme
148 // Keep the navigation-bar icon/affordance contrast in sync with the status bar so
149 // 3-button-nav and gesture-nav devices don't end up with invisible controls on one
150 // theme. The system only guesses when this isn't set explicitly.
151 controller.isAppearanceLightNavigationBars = !darkTheme
155 colorScheme = colorScheme,
156 typography = OpencodeTypography,
157 shapes = OpencodeShapes,
163 * Preview swatches for the Tokyo Night palettes, exposed so the Settings theme picker
164 * can show color dots without reaching into the (otherwise private) full color schemes.
165 * Each list is [primary, secondary, tertiary, background].
167val DarkPaletteSwatches: List<Color> = listOf(
169 DarkColors.secondary,
171 DarkColors.background,
173val LightPaletteSwatches: List<Color> = listOf(
175 LightColors.secondary,
176 LightColors.tertiary,
177 LightColors.background,
179// AMOLED shares the Dark accent colors but uses a pure-black background, so its preview
180// must differ from Dark's (#1A1B26) dot — otherwise the two theme rows are visually
181// indistinguishable in the picker and the user can't tell them apart before selecting.
182val AmoledPaletteSwatches: List<Color> = listOf(
183 AmoledColors.primary,
184 AmoledColors.secondary,
185 AmoledColors.tertiary,
186 AmoledColors.background,
189// Fixed diff add/remove colors per theme (Tokyo Night green/red), exposed so DiffView can
190// render consistent diff semantics regardless of the active palette. Under Material You the
191// primary/error roles can land on hues that don't read as "added" or "removed" (a yellow
192// primary makes an "added" line look like a warning) — a dedicated diff pair preserves the
193// green/red convention every diff viewer uses while staying tuned to the theme's lightness.
194private val DarkDiffAdd = Color(0xFF9ECE6A)
195private val DarkDiffAddBg = Color(0xFF1F3A1F)
196private val DarkDiffRemove = Color(0xFFF7768E)
197private val DarkDiffRemoveBg = Color(0xFF3A1F24)
198private val LightDiffAdd = Color(0xFF485E30)
199private val LightDiffAddBg = Color(0xFFE6F2D0)
200private val LightDiffRemove = Color(0xFF9B1C2E)
201private val LightDiffRemoveBg = Color(0xFFF6D9DD)
203/** A (text, background) pair for added lines in a unified diff. Theme-aware so the green
204 * reads correctly in light/dark/AMOLED instead of relying on the dynamic-color primary. */
205data class DiffColors(val addText: Color, val addBg: Color, val removeText: Color, val removeBg: Color)
208fun diffColors(): DiffColors {
209 // Read the resolved darkTheme flag (which honors ThemeMode/System/AMOLED) instead of the
210 // raw system setting, so a user who forces ThemeMode.LIGHT on a dark system gets the light
211 // diff palette, not the dark one. Computed from the active colorScheme's surface luminance
212 // so it stays correct under dynamic color too.
213 val surface = MaterialTheme.colorScheme.surface
214 val dark = surface.luminance() < 0.5f
215 return if (dark) DiffColors(DarkDiffAdd, DarkDiffAddBg, DarkDiffRemove, DarkDiffRemoveBg)
216 else DiffColors(LightDiffAdd, LightDiffAddBg, LightDiffRemove, LightDiffRemoveBg)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list