Move certain things into separate files

This commit is contained in:
Aleksandras Kostarevas 2023-08-22 23:19:57 +03:00
parent 05fce3bd09
commit 9d4ea1f7c1
12 changed files with 456 additions and 357 deletions

View File

@ -35,8 +35,8 @@ import android.view.View;
import org.futo.inputmethod.keyboard.internal.KeyDrawParams;
import org.futo.inputmethod.keyboard.internal.KeyVisualAttributes;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.DynamicThemeProviderOwner;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner;
import org.futo.inputmethod.latin.R;
import org.futo.inputmethod.latin.common.Constants;
import org.futo.inputmethod.latin.utils.TypefaceUtils;

View File

@ -50,7 +50,7 @@ import org.futo.inputmethod.keyboard.internal.MoreKeySpec;
import org.futo.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
import org.futo.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
import org.futo.inputmethod.keyboard.internal.TimerHandler;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.R;
import org.futo.inputmethod.latin.RichInputMethodSubtype;
import org.futo.inputmethod.latin.SuggestedWords;

View File

@ -26,7 +26,7 @@ import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.R;
public final class KeyPreviewDrawParams {

View File

@ -20,7 +20,7 @@ import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.util.SparseIntArray;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.R;
import org.futo.inputmethod.latin.utils.ResourceUtils;

View File

@ -33,8 +33,8 @@ import org.futo.inputmethod.keyboard.Key;
import org.futo.inputmethod.keyboard.Keyboard;
import org.futo.inputmethod.keyboard.KeyboardId;
import org.futo.inputmethod.keyboard.KeyboardTheme;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.DynamicThemeProviderOwner;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner;
import org.futo.inputmethod.latin.R;
import org.futo.inputmethod.latin.common.Constants;
import org.futo.inputmethod.latin.common.StringUtils;

View File

@ -22,7 +22,7 @@ import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.SparseIntArray;
import org.futo.inputmethod.latin.DynamicThemeProvider;
import org.futo.inputmethod.latin.uix.DynamicThemeProvider;
import org.futo.inputmethod.latin.R;
import java.util.HashMap;

View File

@ -1,58 +1,35 @@
package org.futo.inputmethod.latin
import android.content.Context
import android.content.res.Configuration
import android.content.res.TypedArray
import android.graphics.Color
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.inputmethodservice.InputMethodService
import android.util.TypedValue
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.CompletionInfo
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.view.inputmethod.InputMethodSubtype
import androidx.annotation.ColorInt
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
@ -60,7 +37,6 @@ import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.findViewTreeViewModelStoreOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
@ -68,20 +44,18 @@ import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.material.color.DynamicColors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionBar
import org.futo.inputmethod.latin.uix.BasicThemeProvider
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
import org.futo.inputmethod.latin.uix.THEME_KEY
import org.futo.inputmethod.latin.uix.deferGetSetting
import org.futo.inputmethod.latin.uix.deferSetSetting
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
@ -89,278 +63,9 @@ import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import org.futo.inputmethod.latin.uix.theme.presets.DynamicSystemTheme
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
import kotlin.math.roundToInt
interface DynamicThemeProvider {
val primaryKeyboardColor: Int
val keyboardBackground: Drawable
val keyBackground: Drawable
val spaceBarBackground: Drawable
val keyFeedback: Drawable
val moreKeysKeyboardBackground: Drawable
val popupKey: Drawable
@ColorInt
fun getColor(i: Int): Int?
fun getDrawable(i: Int): Drawable?
companion object {
@ColorInt
fun getColorOrDefault(i: Int, @ColorInt default: Int, keyAttr: TypedArray, provider: DynamicThemeProvider?): Int {
return (provider?.getColor(i)) ?: keyAttr.getColor(i, default)
}
fun getDrawableOrDefault(i: Int, keyAttr: TypedArray, provider: DynamicThemeProvider?): Drawable? {
return (provider?.getDrawable(i)) ?: keyAttr.getDrawable(i)
}
}
}
// TODO: Expand the number of drawables this provides so it covers the full theme, and
// build some system to dynamically change these colors
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 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) {
addState(intArrayOf(android.R.attr.state_pressed) + stateSet, LayerDrawable(arrayOf(
drawable,
coloredRoundedRectangle(highlight, dp(8.dp))
)))
addState(stateSet, drawable)
}
init {
val colorScheme = if(overrideColorScheme != null) {
overrideColorScheme
}else if(!DynamicColors.isDynamicColorAvailable()) {
DarkColorScheme
} else {
val dCtx = DynamicColors.wrapContextIfAvailable(context)
dynamicLightColorScheme(dCtx)
}
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 onSecondary = colorScheme.onSecondary.toArgb()
val onBackground = colorScheme.onBackground.toArgb()
val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).toArgb()
val transparent = Color.TRANSPARENT
colors[R.styleable.Keyboard_Key_keyTextColor] = onBackground
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf
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
drawables[R.styleable.Keyboard_iconDeleteKey] = AppCompatResources.getDrawable(context, R.drawable.delete)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconLanguageSwitchKey] = AppCompatResources.getDrawable(context, R.drawable.globe)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconShiftKey] = AppCompatResources.getDrawable(context, R.drawable.shift)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconShiftKeyShifted] = AppCompatResources.getDrawable(context, R.drawable.shiftshifted)!!.apply {
setTint(onBackground)
}
primaryKeyboardColor = background
keyboardBackground = coloredRectangle(background)
keyBackground = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_active),
coloredRoundedRectangle(primary, dp(8.dp)).apply {
setSize(dp(64.dp).toInt(), dp(48.dp).toInt())
}
)
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),
coloredRectangle(transparent)
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_empty),
coloredRectangle(transparent)
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
coloredRectangle(transparent)
)
}
spaceBarBackground = StateListDrawable().apply {
addState(intArrayOf(android.R.attr.state_pressed),
LayerDrawable(arrayOf(
coloredRoundedRectangle(highlight, dp(32.dp)),
coloredRoundedRectangle(highlight, dp(32.dp))
))
)
addState(intArrayOf(),
coloredRoundedRectangle(highlight, dp(32.dp))
)
}
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())
}
moreKeysKeyboardBackground = coloredRoundedRectangle(surface, dp(8.dp))
popupKey = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
coloredRoundedRectangle(surface, dp(8.dp))
)
}
}
}
interface DynamicThemeProviderOwner {
fun getDrawableProvider(): DynamicThemeProvider
}
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val THEME_KEY = stringPreferencesKey("activeThemeOption")
suspend fun <T> Context.getSetting(key: Preferences.Key<T>, default: T): T {
val valueFlow: Flow<T> =
this.dataStore.data.map { preferences -> preferences[key] ?: default }.take(1)
return valueFlow.first()
}
suspend fun <T> Context.setSetting(key: Preferences.Key<T>, value: T) {
this.dataStore.edit { preferences ->
preferences[key] = value
}
}
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner, LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner,
KeyboardManagerForAction {
private var activeColorScheme = DarkColorScheme
private var colorSchemeLoaderJob: Job? = null
private var drawableProvider: DynamicThemeProvider? = null
override fun getDrawableProvider(): DynamicThemeProvider {
if(drawableProvider == null) {
if(colorSchemeLoaderJob != null && !colorSchemeLoaderJob!!.isCompleted) {
runBlocking {
colorSchemeLoaderJob!!.join()
}
}
drawableProvider = BasicThemeProvider(this, activeColorScheme)
}
return drawableProvider!!
}
private fun recreateKeyboard() {
legacyInputView = latinIMELegacy.onCreateInputView()
latinIMELegacy.loadKeyboard()
}
private fun updateDrawableProvider(colorScheme: ColorScheme) {
activeColorScheme = colorScheme
drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
// recreate the keyboard if not in action window, if we are in action window then
// it'll be recreated when we exit
if(currWindowAction == null) recreateKeyboard()
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
setContent()
}
private val latinIMELegacy = LatinIMELegacy(
this as InputMethodService,
this as LatinIMELegacy.SuggestionStripController
)
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, KeyboardManagerForAction {
private val mSavedStateRegistryController = SavedStateRegistryController.create(this)
@ -368,6 +73,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
get() = mSavedStateRegistryController.savedStateRegistry
private val mLifecycleRegistry = LifecycleRegistry(this)
private fun handleLifecycleEvent(event: Lifecycle.Event) =
mLifecycleRegistry.handleLifecycleEvent(event)
override val lifecycle
get() = mLifecycleRegistry
@ -376,32 +83,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override val viewModelStore
get() = store
private fun handleLifecycleEvent(event: Lifecycle.Event) =
mLifecycleRegistry.handleLifecycleEvent(event)
private val inputMethodManager: InputMethodManager
get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
override fun onCreate() {
super.onCreate()
colorSchemeLoaderJob = lifecycleScope.launch {
var themeKey = this@LatinIME.getSetting(THEME_KEY, DynamicSystemTheme.key)
var themeOption = ThemeOptions[themeKey]
if(themeOption == null || !themeOption.available(this@LatinIME)) {
themeKey = VoiceInputTheme.key
themeOption = ThemeOptions[themeKey]!!
}
activeColorScheme = themeOption.obtainColors(this@LatinIME)
}
mSavedStateRegistryController.performRestore(null)
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
latinIMELegacy.onCreate()
}
private fun setOwners() {
val decorView = window.window?.decorView
if (decorView?.findViewTreeLifecycleOwner() == null) {
@ -415,9 +96,74 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
}
private var composeView: ComposeView? = null
private val latinIMELegacy = LatinIMELegacy(
this as InputMethodService,
this as LatinIMELegacy.SuggestionStripController
)
private var activeColorScheme = DarkColorScheme
private var colorSchemeLoaderJob: Job? = null
private var drawableProvider: DynamicThemeProvider? = null
private var currWindowAction: Action? = null
private fun isActionWindowOpen(): Boolean {
return currWindowAction != null
}
private fun recreateKeyboard() {
legacyInputView = latinIMELegacy.onCreateInputView()
latinIMELegacy.loadKeyboard()
}
private fun updateDrawableProvider(colorScheme: ColorScheme) {
activeColorScheme = colorScheme
drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
// recreate the keyboard if not in action window, if we are in action window then
// it'll be recreated when we exit
if (!isActionWindowOpen()) recreateKeyboard()
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
setContent()
}
override fun getDrawableProvider(): DynamicThemeProvider {
if (drawableProvider == null) {
if (colorSchemeLoaderJob != null && !colorSchemeLoaderJob!!.isCompleted) {
// Must have completed by now!
runBlocking {
colorSchemeLoaderJob!!.join()
}
}
drawableProvider = BasicThemeProvider(this, activeColorScheme)
}
return drawableProvider!!
}
override fun onCreate() {
super.onCreate()
colorSchemeLoaderJob = deferGetSetting(THEME_KEY, DynamicSystemTheme.key) {
var themeKey = it
var themeOption = ThemeOptions[themeKey]
if (themeOption == null || !themeOption.available(this@LatinIME)) {
themeKey = VoiceInputTheme.key
themeOption = ThemeOptions[themeKey]!!
}
activeColorScheme = themeOption.obtainColors(this@LatinIME)
}
mSavedStateRegistryController.performRestore(null)
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
latinIMELegacy.onCreate()
}
override fun onDestroy() {
latinIMELegacy.onDestroy()
super.onDestroy()
@ -450,11 +196,10 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
return composeView!!
}
private var currWindowAction: Action? = null
private fun onActionActivated(action: Action) {
if(action.windowImpl != null) {
if (action.windowImpl != null) {
enterActionWindowView(action)
} else if(action.simplePressImpl != null) {
} else if (action.simplePressImpl != null) {
action.simplePressImpl.invoke(this)
} else {
throw IllegalStateException("An action must have either a window implementation or a simple press implementation")
@ -512,9 +257,11 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
private fun ActionViewWithHeader(action: Action) {
val windowImpl = action.windowImpl!!
Column {
Surface(modifier = Modifier
Surface(
modifier = Modifier
.fillMaxWidth()
.height(40.dp), color = MaterialTheme.colorScheme.background)
.height(40.dp), color = MaterialTheme.colorScheme.background
)
{
Row {
IconButton(onClick = {
@ -526,13 +273,18 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
)
}
Text(windowImpl.windowName(), style = Typography.titleMedium, modifier = Modifier.align(CenterVertically))
Text(
windowImpl.windowName(),
style = Typography.titleMedium,
modifier = Modifier.align(CenterVertically)
)
}
}
Box(modifier = Modifier
.fillMaxWidth()
.height(with(LocalDensity.current) { inputViewHeight.toDp() })) {
.height(with(LocalDensity.current) { inputViewHeight.toDp() })
) {
windowImpl.WindowContents(manager = this@LatinIME)
}
}
@ -561,7 +313,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
legacyInputView = newView
setContent()
if(composeView != null) {
if (composeView != null) {
latinIMELegacy.setComposeInputView(composeView)
}
@ -571,7 +323,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun setInputView(view: View?) {
super.setInputView(view)
if(composeView != null) {
if (composeView != null) {
latinIMELegacy.setComposeInputView(composeView)
}
@ -627,9 +379,23 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
candidatesStart: Int,
candidatesEnd: Int
) {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd)
super.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
latinIMELegacy.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd)
latinIMELegacy.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
}
override fun onExtractedTextClicked() {
@ -653,7 +419,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onComputeInsets(outInsets: Insets?) {
// This method may be called before {@link #setInputView(View)}.
if (legacyInputView == null) {
if (legacyInputView == null || composeView == null) {
return
}
@ -684,7 +450,10 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
override fun onShowInputRequested(flags: Int, configChange: Boolean): Boolean {
return latinIMELegacy.onShowInputRequested(flags, configChange) || super.onShowInputRequested(flags, configChange)
return latinIMELegacy.onShowInputRequested(
flags,
configChange
) || super.onShowInputRequested(flags, configChange)
}
override fun onEvaluateInputViewShown(): Boolean {
@ -735,10 +504,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
assert(newTheme.available(this))
updateDrawableProvider(newTheme.obtainColors(this))
lifecycleScope.launch {
withContext(Dispatchers.Default) {
setSetting(THEME_KEY, newTheme.key)
}
}
deferSetSetting(THEME_KEY, newTheme.key)
}
}

View File

@ -90,6 +90,7 @@ import org.futo.inputmethod.latin.settings.SettingsValues;
import org.futo.inputmethod.latin.suggestions.SuggestionStripView;
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import org.futo.inputmethod.latin.touchinputconsumer.GestureConsumer;
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner;
import org.futo.inputmethod.latin.utils.ApplicationUtils;
import org.futo.inputmethod.latin.utils.DialogUtils;
import org.futo.inputmethod.latin.utils.ImportantNoticeUtils;

View File

@ -0,0 +1,221 @@
package org.futo.inputmethod.latin.uix
import android.content.Context
import android.graphics.Color
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.toArgb
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
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
// TODO: Expand the number of drawables this provides so it covers the full theme, and
// build some system to dynamically change these colors
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 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) {
addState(intArrayOf(android.R.attr.state_pressed) + stateSet, LayerDrawable(
arrayOf(
drawable,
coloredRoundedRectangle(highlight, dp(8.dp))
)
)
)
addState(stateSet, drawable)
}
init {
val colorScheme = if(overrideColorScheme != null) {
overrideColorScheme
}else if(!DynamicColors.isDynamicColorAvailable()) {
DarkColorScheme
} else {
val dCtx = DynamicColors.wrapContextIfAvailable(context)
dynamicLightColorScheme(dCtx)
}
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 onSecondary = colorScheme.onSecondary.toArgb()
val onBackground = colorScheme.onBackground.toArgb()
val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).toArgb()
val transparent = Color.TRANSPARENT
colors[R.styleable.Keyboard_Key_keyTextColor] = onBackground
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf
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
drawables[R.styleable.Keyboard_iconDeleteKey] = AppCompatResources.getDrawable(
context,
R.drawable.delete
)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconLanguageSwitchKey] = AppCompatResources.getDrawable(
context,
R.drawable.globe
)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconShiftKey] = AppCompatResources.getDrawable(
context,
R.drawable.shift
)!!.apply {
setTint(onBackground)
}
drawables[R.styleable.Keyboard_iconShiftKeyShifted] = AppCompatResources.getDrawable(
context,
R.drawable.shiftshifted
)!!.apply {
setTint(onBackground)
}
primaryKeyboardColor = background
keyboardBackground = coloredRectangle(background)
keyBackground = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_active),
coloredRoundedRectangle(primary, dp(8.dp)).apply {
setSize(dp(64.dp).toInt(), dp(48.dp).toInt())
}
)
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),
coloredRectangle(transparent)
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_empty),
coloredRectangle(transparent)
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
coloredRectangle(transparent)
)
}
spaceBarBackground = StateListDrawable().apply {
addState(intArrayOf(android.R.attr.state_pressed),
LayerDrawable(
arrayOf(
coloredRoundedRectangle(highlight, dp(32.dp)),
coloredRoundedRectangle(highlight, dp(32.dp))
)
)
)
addState(intArrayOf(),
coloredRoundedRectangle(highlight, dp(32.dp))
)
}
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())
}
moreKeysKeyboardBackground = coloredRoundedRectangle(surface, dp(8.dp))
popupKey = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
coloredRoundedRectangle(surface, dp(8.dp))
)
}
}
}

View File

@ -0,0 +1,34 @@
package org.futo.inputmethod.latin.uix
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
interface DynamicThemeProvider {
val primaryKeyboardColor: Int
val keyboardBackground: Drawable
val keyBackground: Drawable
val spaceBarBackground: Drawable
val keyFeedback: Drawable
val moreKeysKeyboardBackground: Drawable
val popupKey: Drawable
@ColorInt
fun getColor(i: Int): Int?
fun getDrawable(i: Int): Drawable?
companion object {
@ColorInt
fun getColorOrDefault(i: Int, @ColorInt default: Int, keyAttr: TypedArray, provider: DynamicThemeProvider?): Int {
return (provider?.getColor(i)) ?: keyAttr.getColor(i, default)
}
fun getDrawableOrDefault(i: Int, keyAttr: TypedArray, provider: DynamicThemeProvider?): Drawable? {
return (provider?.getDrawable(i)) ?: keyAttr.getDrawable(i)
}
}
}

View File

@ -0,0 +1,5 @@
package org.futo.inputmethod.latin.uix
interface DynamicThemeProviderOwner {
fun getDrawableProvider(): DynamicThemeProvider
}

View File

@ -0,0 +1,73 @@
package org.futo.inputmethod.latin.uix
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
suspend fun <T> Context.getSetting(key: Preferences.Key<T>, default: T): T {
val valueFlow: Flow<T> =
this.dataStore.data.map { preferences -> preferences[key] ?: default }.take(1)
return valueFlow.first()
}
suspend fun <T> Context.setSetting(key: Preferences.Key<T>, value: T) {
this.dataStore.edit { preferences ->
preferences[key] = value
}
}
fun <T> Context.getSettingBlocking(key: Preferences.Key<T>, default: T): T {
val context = this
return runBlocking {
context.getSetting(key, default)
}
}
fun <T> Context.setSettingBlocking(key: Preferences.Key<T>, value: T) {
val context = this
runBlocking {
context.setSetting(key, value)
}
}
fun <T> LifecycleOwner.deferGetSetting(key: Preferences.Key<T>, default: T, onObtained: (T) -> Unit): Job {
val context = (this as Context)
return lifecycleScope.launch {
withContext(Dispatchers.Default) {
val value = context.getSetting(key, default)
onObtained(value)
}
}
}
fun <T> LifecycleOwner.deferSetSetting(key: Preferences.Key<T>, value: T): Job {
val context = (this as Context)
return lifecycleScope.launch {
withContext(Dispatchers.Default) {
context.setSetting(key, value)
}
}
}
val THEME_KEY = stringPreferencesKey("activeThemeOption")