From a82d894a4b9382cd97006fcd8219ad4c43dcda53 Mon Sep 17 00:00:00 2001
From: Aleksandras Kostarevas <aleks076@protonmail.com>
Date: Mon, 29 Jul 2024 20:41:27 +0300
Subject: [PATCH] Add KeyboardColorScheme to supplement ColorScheme values

---
 .../org/futo/inputmethod/latin/LatinIME.kt    |   9 +-
 .../futo/inputmethod/latin/uix/ActionBar.kt   |  32 +---
 .../latin/uix/BasicThemeProvider.kt           |  44 +----
 .../futo/inputmethod/latin/uix/ColorScheme.kt | 166 ++++++++++++++++++
 .../latin/uix/InlineSuggestionView.kt         |   2 +-
 .../org/futo/inputmethod/latin/uix/Utils.kt   |   2 +-
 .../latin/uix/settings/SettingsActivity.kt    |   4 +-
 .../futo/inputmethod/latin/uix/theme/Theme.kt |  13 +-
 .../latin/uix/theme/ThemeOptions.kt           |   4 +-
 .../uix/theme/presets/AMOLEDDarkPurple.kt     |   3 +-
 .../uix/theme/presets/ClassicMaterialDark.kt  |   3 +-
 .../uix/theme/presets/ClassicMaterialLight.kt |   4 +-
 .../latin/uix/theme/presets/CottonCandy.kt    |   3 +-
 .../latin/uix/theme/presets/DeepSea.kt        |   5 +-
 .../latin/uix/theme/presets/DynamicThemes.kt  |   9 +-
 .../latin/uix/theme/presets/Emerald.kt        |   3 +-
 .../latin/uix/theme/presets/Snowfall.kt       |   3 +-
 .../latin/uix/theme/presets/SteelGrey.kt      |   3 +-
 .../latin/uix/theme/presets/Sunflower.kt      |   3 +-
 .../uix/theme/presets/VoiceInputTheme.kt      |   3 +-
 20 files changed, 222 insertions(+), 96 deletions(-)
 create mode 100644 java/src/org/futo/inputmethod/latin/uix/ColorScheme.kt

diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt
index 7bf84fa0e..082e53016 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -21,7 +21,6 @@ import android.view.inputmethod.InputMethodSubtype
 import androidx.annotation.RequiresApi
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.material3.ColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.key
@@ -60,6 +59,7 @@ import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
 import org.futo.inputmethod.latin.uix.EmojiTracker.unuseEmoji
 import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
 import org.futo.inputmethod.latin.uix.KeyboardBottomOffsetSetting
+import org.futo.inputmethod.latin.uix.KeyboardColorScheme
 import org.futo.inputmethod.latin.uix.SUGGESTION_BLACKLIST
 import org.futo.inputmethod.latin.uix.THEME_KEY
 import org.futo.inputmethod.latin.uix.UixManager
@@ -73,7 +73,6 @@ import org.futo.inputmethod.latin.uix.getSettingBlocking
 import org.futo.inputmethod.latin.uix.getSettingFlow
 import org.futo.inputmethod.latin.uix.isDirectBootUnlocked
 import org.futo.inputmethod.latin.uix.setSetting
-import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.ThemeOptions
 import org.futo.inputmethod.latin.uix.theme.applyWindowColors
@@ -125,7 +124,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
     lateinit var suggestionBlacklist: SuggestionBlacklist
 
     private var activeThemeOption: ThemeOption? = null
-    private var activeColorScheme = DarkColorScheme
+    private var activeColorScheme = VoiceInputTheme.obtainColors(this)
     private var pendingRecreateKeyboard: Boolean = false
 
     val themeOption get() = activeThemeOption
@@ -172,9 +171,9 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
         }
     }
 
-    private fun updateDrawableProvider(colorScheme: ColorScheme) {
+    private fun updateDrawableProvider(colorScheme: KeyboardColorScheme) {
         activeColorScheme = colorScheme
-        drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
+        drawableProvider = BasicThemeProvider(this, colorScheme)
 
         updateNavigationBarVisibility()
         uixManager.onColorSchemeChanged()
diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
index c464021b8..b73143fa8 100644
--- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
@@ -408,11 +408,7 @@ fun LazyItemScope.ActionItem(idx: Int, action: Action, onSelect: (Action) -> Uni
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun ActionItemSmall(action: Action, onSelect: (Action) -> Unit, onLongSelect: (Action) -> Unit) {
-    val bgCol = if(!LocalInspectionMode.current) {
-        Color(LocalThemeProvider.current.keyColor)
-    } else {
-        MaterialTheme.colorScheme.surfaceVariant
-    }
+    val bgCol = LocalKeyboardScheme.current.backgroundContainer
 
     val circleRadius = with(LocalDensity.current) {
         16.dp.toPx()
@@ -480,11 +476,7 @@ fun ActionItems(onSelect: (Action) -> Unit, onLongSelect: (Action) -> Unit) {
         }
     }
 
-    val bgCol = if(!LocalInspectionMode.current) {
-        Color(LocalThemeProvider.current.keyColor)
-    } else {
-        MaterialTheme.colorScheme.surfaceVariant
-    }
+    val bgCol = LocalKeyboardScheme.current.backgroundContainer
 
     val gradientColor = if(bgCol.alpha > 0.5) {
         bgCol.copy(alpha = 0.9f)
@@ -546,11 +538,7 @@ fun ActionItems(onSelect: (Action) -> Unit, onLongSelect: (Action) -> Unit) {
 
 @Composable
 fun ExpandActionsButton(isActionsOpen: Boolean, onClick: () -> Unit) {
-    val bgCol = if(!LocalInspectionMode.current) {
-        Color(LocalThemeProvider.current.keyColor)
-    } else {
-        MaterialTheme.colorScheme.surfaceVariant
-    }
+    val bgCol = LocalKeyboardScheme.current.backgroundContainer
 
     val actionsContent = MaterialTheme.colorScheme.onSurface
 
@@ -646,11 +634,7 @@ fun RowScope.PinnedActionItems(onSelect: (Action) -> Unit, onLongSelect: (Action
 
 @Composable
 fun ActionSep() {
-    val sepCol = if(!LocalInspectionMode.current) {
-        Color(LocalThemeProvider.current.keyColor)
-    } else {
-        MaterialTheme.colorScheme.surfaceVariant
-    }
+    val sepCol = LocalKeyboardScheme.current.backgroundContainer
 
     Box(modifier = Modifier
         .fillMaxWidth()
@@ -914,7 +898,7 @@ val exampleSuggestedWordsEmpty = SuggestedWords(
 @Composable
 @Preview
 fun PreviewActionBarWithSuggestions(colorScheme: ColorScheme = DarkColorScheme) {
-    UixThemeWrapper(colorScheme) {
+    UixThemeWrapper(wrapColorScheme(colorScheme)) {
         ActionBar(
             words = exampleSuggestedWords,
             suggestionStripListener = ExampleListener(),
@@ -930,7 +914,7 @@ fun PreviewActionBarWithSuggestions(colorScheme: ColorScheme = DarkColorScheme)
 @Composable
 @Preview
 fun PreviewActionBarWithNotice(colorScheme: ColorScheme = DarkColorScheme) {
-    UixThemeWrapper(colorScheme) {
+    UixThemeWrapper(wrapColorScheme(colorScheme)) {
         ActionBar(
             words = exampleSuggestedWords,
             suggestionStripListener = ExampleListener(),
@@ -961,7 +945,7 @@ fun PreviewActionBarWithNotice(colorScheme: ColorScheme = DarkColorScheme) {
 @Composable
 @Preview
 fun PreviewActionBarWithEmptySuggestions(colorScheme: ColorScheme = DarkColorScheme) {
-    UixThemeWrapper(colorScheme) {
+    UixThemeWrapper(wrapColorScheme(colorScheme)) {
         ActionBar(
             words = exampleSuggestedWordsEmpty,
             suggestionStripListener = ExampleListener(),
@@ -977,7 +961,7 @@ fun PreviewActionBarWithEmptySuggestions(colorScheme: ColorScheme = DarkColorSch
 @Composable
 @Preview
 fun PreviewExpandedActionBar(colorScheme: ColorScheme = DarkColorScheme) {
-    UixThemeWrapper(colorScheme) {
+    UixThemeWrapper(wrapColorScheme(colorScheme)) {
         ActionBar(
             words = exampleSuggestedWordsEmpty,
             suggestionStripListener = ExampleListener(),
diff --git a/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt b/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt
index d351398e7..4a20f4436 100644
--- a/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt
@@ -36,34 +36,11 @@ val KeyHintsSetting   = SettingsKey(booleanPreferencesKey("keyHints"), false)
 val KeyboardHeightMultiplierSetting = SettingsKey(floatPreferencesKey("keyboardHeightMultiplier"), 1.0f)
 val KeyboardBottomOffsetSetting = SettingsKey(floatPreferencesKey("keyboardOffset"), 0.0f)
 
-fun adjustColorBrightnessForContrast(bgColor: Int, fgColor: Int, desiredContrast: Float, adjustSaturation: Boolean = false): Int {
-    // Convert RGB colors to HSL
-    val bgHSL = FloatArray(3)
-    ColorUtils.colorToHSL(bgColor, bgHSL)
-    val fgHSL = FloatArray(3)
-    ColorUtils.colorToHSL(fgColor, fgHSL)
-
-    // Estimate the adjustment needed in lightness to achieve the desired contrast
-    // This is a simplified approach and may not be perfectly accurate
-    val lightnessAdjustment = (desiredContrast - 1) / 10.0f // Simplified and heuristic-based adjustment
-
-    // Adjust the background color's lightness
-    bgHSL[2] = bgHSL[2] + lightnessAdjustment
-    bgHSL[2] = bgHSL[2].coerceIn(0f, 1f) // Ensure the lightness stays within valid range
-
-    if(adjustSaturation) {
-        bgHSL[1] = (bgHSL[1] + lightnessAdjustment).coerceIn(0f, 1f)
-    }
-
-    // Convert back to RGB and return the adjusted color
-    return ColorUtils.HSLToColor(bgHSL)
-}
-
 fun<T> Preferences.get(key: SettingsKey<T>): T {
     return this[key.key] ?: key.default
 }
 
-class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorScheme? = null) :
+class BasicThemeProvider(val context: Context, val colorScheme: KeyboardColorScheme) :
     DynamicThemeProvider {
     override val primaryKeyboardColor: Int
     override val keyColor: Int
@@ -175,16 +152,6 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
     }
 
     init {
-        val colorScheme = if(overrideColorScheme != null) {
-            overrideColorScheme
-        }else if(!DynamicColors.isDynamicColorAvailable()) {
-            DarkColorScheme
-        } else {
-            val dCtx = DynamicColors.wrapContextIfAvailable(context)
-
-            dynamicLightColorScheme(dCtx)
-        }
-
         expertMode = context.getSettingBlocking(HiddenKeysSetting)
         keyBorders = context.getSettingBlocking(KeyBordersSetting)
         showKeyHints = context.getSettingBlocking(KeyHintsSetting)
@@ -217,12 +184,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
 
         val ratio = 1.5f
         val keyColor = if(keyBorders) {
-            var c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, ratio)
-            if(c == primaryKeyboardColor) {
-                // May happen if the color is already 100% white
-                c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, 1.0f / (ratio / 2.0f + 0.5f))
-            }
-            c
+            colorScheme.backgroundContainer.toArgb()
         } else {
             transparent
         }
@@ -230,7 +192,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
         this.keyColor = keyColor
 
         val functionalKeyColor = if(keyBorders) {
-            adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, ratio / 2.0f + 0.5f, adjustSaturation = true)
+            colorScheme.backgroundContainerDim.toArgb()
         } else {
             transparent
         }
diff --git a/java/src/org/futo/inputmethod/latin/uix/ColorScheme.kt b/java/src/org/futo/inputmethod/latin/uix/ColorScheme.kt
new file mode 100644
index 000000000..aeee3f613
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/ColorScheme.kt
@@ -0,0 +1,166 @@
+package org.futo.inputmethod.latin.uix
+
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.core.graphics.ColorUtils
+
+val LocalKeyboardScheme = staticCompositionLocalOf {
+    wrapColorScheme(lightColorScheme())
+}
+
+class ExtraColors(
+    val backgroundContainer: Color,
+    val backgroundContainerDim: Color,
+    val onBackgroundContainer: Color,
+    val outlineBright: Color,
+    val outlineDim: Color,
+)
+
+data class KeyboardColorScheme(
+    val base: ColorScheme,
+    val extended: ExtraColors
+) {
+    // Base colors
+    val primary: Color
+        get() = base.primary
+    val onPrimary: Color
+        get() = base.onPrimary
+    val primaryContainer: Color
+        get() = base.primaryContainer
+    val onPrimaryContainer: Color
+        get() = base.onPrimaryContainer
+    val inversePrimary: Color
+        get() = base.inversePrimary
+    val secondary: Color
+        get() = base.secondary
+    val onSecondary: Color
+        get() = base.onSecondary
+    val secondaryContainer: Color
+        get() = base.secondaryContainer
+    val onSecondaryContainer: Color
+        get() = base.onSecondaryContainer
+    val tertiary: Color
+        get() = base.tertiary
+    val onTertiary: Color
+        get() = base.onTertiary
+    val tertiaryContainer: Color
+        get() = base.tertiaryContainer
+    val onTertiaryContainer: Color
+        get() = base.onTertiaryContainer
+    val background: Color
+        get() = base.background
+    val onBackground: Color
+        get() = base.onBackground
+    val surface: Color
+        get() = base.surface
+    val onSurface: Color
+        get() = base.onSurface
+    val surfaceVariant: Color
+        get() = base.surfaceVariant
+    val onSurfaceVariant: Color
+        get() = base.onSurfaceVariant
+    val surfaceTint: Color
+        get() = base.surfaceTint
+    val inverseSurface: Color
+        get() = base.inverseSurface
+    val inverseOnSurface: Color
+        get() = base.inverseOnSurface
+    val error: Color
+        get() = base.error
+    val onError: Color
+        get() = base.onError
+    val errorContainer: Color
+        get() = base.errorContainer
+    val onErrorContainer: Color
+        get() = base.onErrorContainer
+    val outline: Color
+        get() = base.outline
+    val outlineVariant: Color
+        get() = base.outlineVariant
+    val scrim: Color
+        get() = base.scrim
+    val surfaceBright: Color
+        get() = base.surfaceBright
+    val surfaceDim: Color
+        get() = base.surfaceDim
+    val surfaceContainer: Color
+        get() = base.surfaceContainer
+    val surfaceContainerHigh: Color
+        get() = base.surfaceContainerHigh
+    val surfaceContainerHighest: Color
+        get() = base.surfaceContainerHighest
+    val surfaceContainerLow: Color
+        get() = base.surfaceContainerLow
+    val surfaceContainerLowest: Color
+        get() = base.surfaceContainerLowest
+
+    // Extended colors
+    val backgroundContainer: Color
+        get() = extended.backgroundContainer
+    val backgroundContainerDim: Color
+        get() = extended.backgroundContainerDim
+    val onBackgroundContainer: Color
+        get() = extended.onBackgroundContainer
+    val outlineBright: Color
+        get() = extended.outlineBright
+    val outlineDim: Color
+        get() = extended.outlineDim
+}
+
+fun wrapColorScheme(base: ColorScheme): KeyboardColorScheme =
+    KeyboardColorScheme(
+        base,
+        generateExtraColorsAutomatically(base)
+    )
+
+private fun generateExtraColorsAutomatically(base: ColorScheme): ExtraColors {
+    val ratio = 1.5f
+    val background = base.background.toArgb()
+
+    var backgroundContainer = adjustColorBrightnessForContrast(background, background, 1.5f)
+    if(backgroundContainer == background) {
+        // May happen if the color is already 100% white
+        backgroundContainer = adjustColorBrightnessForContrast(background, background, 1.0f / (ratio / 2.0f + 0.5f))
+    }
+
+    val backgroundContainerDim = adjustColorBrightnessForContrast(background, background, ratio / 2.0f + 0.5f, adjustSaturation = true)
+
+    val onBackgroundContainer = base.onBackground
+
+    val outlineDim = base.outline.copy(alpha = 0.5f)
+    val outlineBright = base.outline.copy(alpha = 1.0f)
+
+    return ExtraColors(
+        backgroundContainer = Color(backgroundContainer),
+        backgroundContainerDim = Color(backgroundContainerDim),
+        onBackgroundContainer = onBackgroundContainer,
+        outlineDim = outlineDim,
+        outlineBright = outlineBright
+    )
+}
+
+private fun adjustColorBrightnessForContrast(bgColor: Int, fgColor: Int, desiredContrast: Float, adjustSaturation: Boolean = false): Int {
+    // Convert RGB colors to HSL
+    val bgHSL = FloatArray(3)
+    ColorUtils.colorToHSL(bgColor, bgHSL)
+    val fgHSL = FloatArray(3)
+    ColorUtils.colorToHSL(fgColor, fgHSL)
+
+    // Estimate the adjustment needed in lightness to achieve the desired contrast
+    // This is a simplified approach and may not be perfectly accurate
+    val lightnessAdjustment = (desiredContrast - 1) / 10.0f // Simplified and heuristic-based adjustment
+
+    // Adjust the background color's lightness
+    bgHSL[2] = bgHSL[2] + lightnessAdjustment
+    bgHSL[2] = bgHSL[2].coerceIn(0f, 1f) // Ensure the lightness stays within valid range
+
+    if(adjustSaturation) {
+        bgHSL[1] = (bgHSL[1] + lightnessAdjustment).coerceIn(0f, 1f)
+    }
+
+    // Convert back to RGB and return the adjusted color
+    return ColorUtils.HSLToColor(bgHSL)
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt b/java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt
index ecdc26071..0edfd886f 100644
--- a/java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt
@@ -50,7 +50,7 @@ private const val maxHeightDp = 48.0f
 @RequiresApi(Build.VERSION_CODES.R)
 fun createInlineSuggestionsRequest(
     context: Context,
-    activeColorScheme: ColorScheme
+    activeColorScheme: KeyboardColorScheme
 ): InlineSuggestionsRequest {
     val fromDp = { v: Float ->
         context.fromDp(v).roundToInt()
diff --git a/java/src/org/futo/inputmethod/latin/uix/Utils.kt b/java/src/org/futo/inputmethod/latin/uix/Utils.kt
index b406b2582..ca165959c 100644
--- a/java/src/org/futo/inputmethod/latin/uix/Utils.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/Utils.kt
@@ -20,7 +20,7 @@ import java.net.URLDecoder
 import java.net.URLEncoder
 
 // Not exhaustive
-fun ColorScheme.differsFrom(other: ColorScheme): Boolean {
+fun KeyboardColorScheme.differsFrom(other: KeyboardColorScheme): Boolean {
     return this.background != other.background
             || this.surface != other.surface
             || this.primary != other.primary
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
index 0802f024c..8a16ec802 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
@@ -208,7 +208,7 @@ class SettingsActivity : ComponentActivity(), DynamicThemeProviderOwner {
                 this@SettingsActivity.themeOption.value = themeOption
                 this@SettingsActivity.themeProvider = BasicThemeProvider(
                     context = this@SettingsActivity,
-                    overrideColorScheme = themeOption.obtainColors(this@SettingsActivity)
+                    colorScheme = themeOption.obtainColors(this@SettingsActivity)
                 )
 
                 updateEdgeToEdge()
@@ -226,7 +226,7 @@ class SettingsActivity : ComponentActivity(), DynamicThemeProviderOwner {
             this.themeOption.value = themeOption
             this.themeProvider = BasicThemeProvider(
                 context = this@SettingsActivity,
-                overrideColorScheme = themeOption.obtainColors(this@SettingsActivity)
+                colorScheme = themeOption.obtainColors(this@SettingsActivity)
             )
 
             updateEdgeToEdge()
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt b/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt
index 04eb8f904..1490966c9 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt
@@ -10,10 +10,13 @@ import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalContext
+import org.futo.inputmethod.latin.uix.KeyboardColorScheme
+import org.futo.inputmethod.latin.uix.LocalKeyboardScheme
 import org.futo.inputmethod.latin.uix.THEME_KEY
 import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
 import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
@@ -84,12 +87,14 @@ fun StatusBarColorSetter() {
 }
 
 @Composable
-fun UixThemeWrapper(colorScheme: ColorScheme, content: @Composable () -> Unit) {
-    MaterialTheme(
-            colorScheme = colorScheme,
+fun UixThemeWrapper(colorScheme: KeyboardColorScheme, content: @Composable () -> Unit) {
+    CompositionLocalProvider(LocalKeyboardScheme provides colorScheme) {
+        MaterialTheme(
+            colorScheme = colorScheme.base,
             typography = Typography,
             content = content,
-    )
+        )
+    }
 }
 
 fun ThemeOption?.ensureAvailable(context: Context): ThemeOption? {
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/ThemeOptions.kt b/java/src/org/futo/inputmethod/latin/uix/theme/ThemeOptions.kt
index a96c8c0a4..1200044bd 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/ThemeOptions.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/ThemeOptions.kt
@@ -2,7 +2,7 @@ package org.futo.inputmethod.latin.uix.theme
 
 import android.content.Context
 import androidx.annotation.StringRes
-import androidx.compose.material3.ColorScheme
+import org.futo.inputmethod.latin.uix.KeyboardColorScheme
 import org.futo.inputmethod.latin.uix.theme.presets.AMOLEDDarkPurple
 import org.futo.inputmethod.latin.uix.theme.presets.ClassicMaterialDark
 import org.futo.inputmethod.latin.uix.theme.presets.ClassicMaterialLight
@@ -23,7 +23,7 @@ data class ThemeOption(
     val key: String,
     @StringRes val name: Int,
     val available: (Context) -> Boolean,
-    val obtainColors: (Context) -> ColorScheme,
+    val obtainColors: (Context) -> KeyboardColorScheme,
 )
 
 val ThemeOptions = mapOf(
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/AMOLEDDarkPurple.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/AMOLEDDarkPurple.kt
index 30a7cf10f..dcfeea640 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/AMOLEDDarkPurple.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/AMOLEDDarkPurple.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val md_theme_dark_primary = Color(0xFFD0BCFF)
 private val md_theme_dark_onPrimary = Color(0xFF381E72)
@@ -77,7 +78,7 @@ val AMOLEDDarkPurple = ThemeOption(
     name = R.string.amoled_dark_theme_name,
     available = { true }
 ) {
-    colorScheme
+    wrapColorScheme(colorScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialDark.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialDark.kt
index 0a9306974..a6f9fa212 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialDark.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialDark.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 
 private val md_theme_dark_primary = Color(0xFF80cbc4)
@@ -92,7 +93,7 @@ val ClassicMaterialDark = ThemeOption(
     name = R.string.classic_material_dark_theme_name,
     available = { true }
 ) {
-    colorScheme
+    wrapColorScheme(colorScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialLight.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialLight.kt
index 2ff6bf798..c7aabf548 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialLight.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/ClassicMaterialLight.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
-
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 
 val md_theme_light_primary = Color(0xFF4db6ac)
@@ -94,7 +94,7 @@ val ClassicMaterialLight = ThemeOption(
     name = R.string.classic_material_light_theme_name,
     available = { true }
 ) {
-    colorScheme
+    wrapColorScheme(colorScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/CottonCandy.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/CottonCandy.kt
index aadbeff63..fca7012cd 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/CottonCandy.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/CottonCandy.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val primaryLight = Color(0xFFEC5EB4)
 private val onPrimaryLight = Color(0xFFFFFFFF)
@@ -90,7 +91,7 @@ val CottonCandy = ThemeOption(
     name = R.string.cotton_candy_theme_name,
     available = { true }
 ) {
-    lightScheme
+    wrapColorScheme(lightScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/DeepSea.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/DeepSea.kt
index c64bb5a9d..6dd8c412f 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/DeepSea.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/DeepSea.kt
@@ -8,6 +8,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 
 private val primaryLight = Color(0xFF4062D3)
@@ -164,7 +165,7 @@ val DeepSeaLight = ThemeOption(
     name = R.string.deep_sea_light_theme_name,
     available = { true }
 ) {
-    lightScheme
+    wrapColorScheme(lightScheme)
 }
 
 val DeepSeaDark = ThemeOption(
@@ -173,7 +174,7 @@ val DeepSeaDark = ThemeOption(
     name = R.string.deep_sea_dark_theme_name,
     available = { true }
 ) {
-    darkScheme
+    wrapColorScheme(darkScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/DynamicThemes.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/DynamicThemes.kt
index 42623f17b..0728f08e7 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/DynamicThemes.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/DynamicThemes.kt
@@ -8,6 +8,7 @@ import androidx.compose.material3.dynamicDarkColorScheme
 import androidx.compose.material3.dynamicLightColorScheme
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 val DynamicSystemTheme = ThemeOption(
     dynamic = true,
@@ -20,7 +21,7 @@ val DynamicSystemTheme = ThemeOption(
         }
 
         val uiModeManager = it.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
-        when (uiModeManager.nightMode) {
+        wrapColorScheme(when (uiModeManager.nightMode) {
             UiModeManager.MODE_NIGHT_YES -> dynamicDarkColorScheme(it)
             UiModeManager.MODE_NIGHT_NO -> dynamicLightColorScheme(it)
             else -> {
@@ -31,7 +32,7 @@ val DynamicSystemTheme = ThemeOption(
                     dynamicDarkColorScheme(it)
                 }
             }
-        }
+        })
     }
 )
 
@@ -45,7 +46,7 @@ val DynamicDarkTheme = ThemeOption(
             throw IllegalStateException("DynamicDarkTheme obtainColors called when available() == false")
         }
 
-        dynamicDarkColorScheme(it)
+        wrapColorScheme(dynamicDarkColorScheme(it))
     }
 )
 
@@ -59,6 +60,6 @@ val DynamicLightTheme = ThemeOption(
             throw IllegalStateException("DynamicLightTheme obtainColors called when available() == false")
         }
 
-        dynamicLightColorScheme(it)
+        wrapColorScheme(dynamicLightColorScheme(it))
     }
 )
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Emerald.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Emerald.kt
index 0afa5d30a..8fdf1f923 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Emerald.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Emerald.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val primaryDark = Color(0xFF67FB59)
 private val onPrimaryDark = Color(0xFF003A02)
@@ -88,7 +89,7 @@ val Emerald = ThemeOption(
     name = R.string.emerald_theme_name,
     available = { true }
 ) {
-    darkScheme
+    wrapColorScheme(darkScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Snowfall.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Snowfall.kt
index db91ac2d8..add8a7bef 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Snowfall.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Snowfall.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val primaryLightHighContrast = Color(0xFF212223)
 private val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
@@ -89,7 +90,7 @@ val Snowfall = ThemeOption(
     name = R.string.snowfall_theme_name,
     available = { true }
 ) {
-    highContrastLightColorScheme
+    wrapColorScheme(highContrastLightColorScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/SteelGrey.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/SteelGrey.kt
index 1af2a98c6..19be1b2f2 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/SteelGrey.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/SteelGrey.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val primaryLightHighContrast = Color(0xFF212223)
 private val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
@@ -89,7 +90,7 @@ val SteelGray = ThemeOption(
     name = R.string.steel_gray_theme_name,
     available = { true }
 ) {
-    highContrastLightColorScheme
+    wrapColorScheme(highContrastLightColorScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Sunflower.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Sunflower.kt
index 328037ad0..e71164d65 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/Sunflower.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/Sunflower.kt
@@ -7,6 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 private val primaryLight = Color(0xFF6D5E0F)
 private val onPrimaryLight = Color(0xFFFFFFFF)
@@ -89,7 +90,7 @@ val Sunflower = ThemeOption(
     name = R.string.sunflower_theme_name,
     available = { true }
 ) {
-    lightScheme
+    wrapColorScheme(lightScheme)
 }
 
 @Composable
diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/presets/VoiceInputTheme.kt b/java/src/org/futo/inputmethod/latin/uix/theme/presets/VoiceInputTheme.kt
index f772abb7f..76b55d693 100644
--- a/java/src/org/futo/inputmethod/latin/uix/theme/presets/VoiceInputTheme.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/theme/presets/VoiceInputTheme.kt
@@ -6,6 +6,7 @@ import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.selector.ThemePreview
+import org.futo.inputmethod.latin.uix.wrapColorScheme
 
 val VoiceInputTheme = ThemeOption(
     dynamic = false,
@@ -13,7 +14,7 @@ val VoiceInputTheme = ThemeOption(
     name = R.string.voice_input_theme_name,
     available = { true }
 ) {
-    DarkColorScheme
+    wrapColorScheme(DarkColorScheme)
 }
 
 @Composable