Newer
Older
package org.futo.inputmethod.latin.uix
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.StateListDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.util.TypedValue
import androidx.annotation.ColorInt
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.ColorUtils
import androidx.datastore.preferences.core.booleanPreferencesKey
import com.google.android.material.color.DynamicColors
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import kotlin.math.roundToInt
val KeyBordersSetting = booleanPreferencesKey("keyBorders")
val HiddenKeysSetting = booleanPreferencesKey("hiddenKeys")
val KeyHintsSetting = booleanPreferencesKey("keyHints")
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)
}
class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorScheme? = null) :
DynamicThemeProvider {
override val primaryKeyboardColor: Int
override val keyboardBackground: Drawable
override val keyBackground: Drawable
override val spaceBarBackground: Drawable
override val keyFeedback: Drawable
override val moreKeysTextColor: Int
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
override val moreKeysKeyboardBackground: Drawable
override val popupKey: Drawable
private val colors: HashMap<Int, Int> = HashMap()
override fun getColor(i: Int): Int? {
return colors[i]
}
private val drawables: HashMap<Int, Drawable> = HashMap()
override fun getDrawable(i: Int): Drawable? {
return drawables[i]
}
private fun dp(dp: Dp): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.value,
context.resources.displayMetrics
);
}
private fun coloredRectangle(@ColorInt color: Int): GradientDrawable {
return GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
setColor(color)
}
}
private fun coloredRoundedRectangle(@ColorInt color: Int, radius: Float): GradientDrawable {
return GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = radius
setColor(color)
}
}
private fun coloredOval(@ColorInt color: Int): GradientDrawable {
return GradientDrawable().apply {
shape = GradientDrawable.OVAL
cornerRadius = Float.MAX_VALUE
setColor(color)
}
}
private fun StateListDrawable.addStateWithHighlightLayerOnPressed(@ColorInt highlight: Int, stateSet: IntArray, drawable: Drawable, cornerRadius: Dp = 8.dp) {
addState(intArrayOf(android.R.attr.state_pressed) + stateSet, LayerDrawable(
arrayOf(
drawable,
coloredRoundedRectangle(highlight, dp(cornerRadius))
)
)
)
addState(stateSet, drawable)
}
val expertMode: Boolean
val keyBorders: Boolean
val showKeyHints: Boolean
init {
val colorScheme = if(overrideColorScheme != null) {
overrideColorScheme
}else if(!DynamicColors.isDynamicColorAvailable()) {
DarkColorScheme
} else {
val dCtx = DynamicColors.wrapContextIfAvailable(context)
dynamicLightColorScheme(dCtx)
}
expertMode = context.getSettingBlocking(HiddenKeysSetting, false)
keyBorders = context.getSettingBlocking(KeyBordersSetting, false)
showKeyHints = context.getSettingBlocking(KeyHintsSetting, false)
val primary = colorScheme.primary.toArgb()
val secondary = colorScheme.secondary.toArgb()
val highlight = colorScheme.outline.copy(alpha = 0.33f).toArgb()
val background = colorScheme.surface.toArgb()
val surface = colorScheme.background.toArgb()
val outline = colorScheme.outline.toArgb()
val primaryContainer = colorScheme.primaryContainer.toArgb()
val onPrimaryContainer = colorScheme.onPrimaryContainer.toArgb()
val onPrimary = colorScheme.onPrimary.toArgb()
val onPrimaryThird = colorScheme.onPrimary.copy(alpha = 0.33f).toArgb()
val onSecondary = colorScheme.onSecondary.toArgb()
val onBackground = colorScheme.onBackground.toArgb()
val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).toArgb()
val onBackgroundThird = colorScheme.onBackground.copy(alpha = 0.33f).toArgb()
val transparent = Color.Transparent.toArgb()
primaryKeyboardColor = if(keyBorders) {
colorScheme.background.toArgb()
} else {
colorScheme.surface.toArgb()
}
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
} else {
transparent
}
val functionalKeyColor = if(keyBorders) {
adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, ratio / 2.0f + 0.5f, adjustSaturation = true)
} else {
transparent
}
val enterKeyBackground = if(expertMode) { functionalKeyColor } else { primary }
val enterKeyForeground = if(expertMode) { onBackgroundThird } else { onPrimary }
colors[R.styleable.Keyboard_Key_keyTextColor] = onBackground
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf
colors[R.styleable.Keyboard_Key_keyPressedTextColor] = onPrimary
colors[R.styleable.Keyboard_Key_keyTextShadowColor] = 0
colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackground
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = onBackgroundHalf
colors[R.styleable.Keyboard_Key_keyHintLabelColor] = onBackgroundHalf
colors[R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor] = onBackgroundHalf
colors[R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor] = onBackgroundHalf
colors[R.styleable.Keyboard_Key_keyPreviewTextColor] = onSecondary
colors[R.styleable.MainKeyboardView_languageOnSpacebarTextColor] = onBackgroundHalf
colors[R.styleable.MainKeyboardView_gestureTrailColor] = primary
val overrideDrawable: (Int, Int, Int) -> Unit = { a, b, color ->
drawables[a] = AppCompatResources.getDrawable(
context,
b
)!!.apply {
setTint(color)
}
// No good replacements for these icons yet, but we set them anyway for setTint
overrideDrawable(R.styleable.Keyboard_iconEnterKey, R.drawable.sym_keyboard_return_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconGoKey, R.drawable.sym_keyboard_go_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconNextKey, R.drawable.sym_keyboard_next_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconDoneKey, R.drawable.sym_keyboard_done_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconPreviousKey, R.drawable.sym_keyboard_previous_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconSearchKey, R.drawable.sym_keyboard_search_lxx_light, enterKeyForeground)
overrideDrawable(R.styleable.Keyboard_iconDeleteKey, R.drawable.delete, onBackground)
overrideDrawable(R.styleable.Keyboard_iconSettingsKey, R.drawable.settings, onBackground)
overrideDrawable(R.styleable.Keyboard_iconEmojiActionKey, R.drawable.smile, onPrimary)
overrideDrawable(R.styleable.Keyboard_iconEmojiNormalKey, R.drawable.smile, onBackground)
overrideDrawable(R.styleable.Keyboard_iconLanguageSwitchKey, R.drawable.globe, onBackground)
overrideDrawable(R.styleable.Keyboard_iconShiftKey, R.drawable.shift, onBackground)
overrideDrawable(R.styleable.Keyboard_iconShiftKeyShifted, R.drawable.shiftshifted, onBackground)
if(!showKeyHints) {
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLabelColor] = transparent
}
if(expertMode) {
colors[R.styleable.Keyboard_Key_keyTextColor] = transparent
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLabelColor] = transparent
// Note: We don't fully hide some things, but fade them away as they may be important landmarks
colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackgroundThird
overrideDrawable(R.styleable.Keyboard_iconShiftKey, R.drawable.shift, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconShiftKeyShifted, R.drawable.shiftshifted, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconDeleteKey, R.drawable.delete, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconEmojiNormalKey, R.drawable.smile, transparent)
}
keyboardBackground = coloredRectangle(primaryKeyboardColor)
keyBackground = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_active),
coloredRoundedRectangle(enterKeyBackground, dp(128.dp)),
cornerRadius = 128.dp
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_checkable, android.R.attr.state_checked),
coloredRoundedRectangle(colorScheme.secondaryContainer.toArgb(), dp(8.dp))
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_checkable),
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_first),
if(keyBorders) {
coloredRoundedRectangle(functionalKeyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_empty),
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
val spaceCornerRadius = if(keyBorders) {
8.dp
} else {
48.dp
}
val spaceDrawable = if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(spaceCornerRadius))
} else if(expertMode) {
coloredRoundedRectangle(colorScheme.outline.copy(alpha = 0.1f).toArgb(), dp(spaceCornerRadius))
} else {
coloredRoundedRectangle(highlight, dp(spaceCornerRadius))
}
spaceBarBackground = StateListDrawable().apply {
addState(intArrayOf(android.R.attr.state_pressed),
LayerDrawable(
arrayOf(
spaceDrawable,
coloredRoundedRectangle(highlight, dp(spaceCornerRadius))
)
)
)
addState(intArrayOf(),
spaceDrawable
)
}
keyFeedback = ShapeDrawable().apply {
paint.color = secondary
shape = RoundRectShape(
floatArrayOf(
dp(8.dp), dp(8.dp), dp(8.dp), dp(8.dp),
dp(8.dp), dp(8.dp), dp(8.dp), dp(8.dp),
), null, null
)
intrinsicWidth = dp(48.dp).roundToInt()
intrinsicHeight = dp(24.dp).roundToInt()
setPadding(0, 0, 0, dp(50.dp).roundToInt())
}
moreKeysTextColor = onPrimaryContainer
moreKeysKeyboardBackground = coloredRoundedRectangle(primaryContainer, dp(8.dp))
popupKey = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(primary, intArrayOf(),
coloredRoundedRectangle(primaryContainer, dp(8.dp))
)
}
}
}