Move themes to separate files, save theme choice

This commit is contained in:
Aleksandras Kostarevas 2023-08-22 20:37:51 +03:00
parent 106de18b3b
commit d11025192e
9 changed files with 378 additions and 242 deletions

View File

@ -128,6 +128,8 @@ dependencies {
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'androidx.datastore:datastore-preferences:1.0.0'
debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest' debugImplementation 'androidx.compose.ui:ui-test-manifest'

View File

@ -48,6 +48,11 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView 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.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
@ -55,6 +60,7 @@ import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistry
@ -63,13 +69,26 @@ import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.material.color.DynamicColors 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.common.Constants
import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionBar import org.futo.inputmethod.latin.uix.ActionBar
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme 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.Typography import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper 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 import kotlin.math.roundToInt
interface DynamicThemeProvider { interface DynamicThemeProvider {
@ -285,14 +304,36 @@ interface DynamicThemeProviderOwner {
} }
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, class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner, LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner,
KeyboardManagerForAction { KeyboardManagerForAction {
private var activeColorScheme = DarkColorScheme private var activeColorScheme = DarkColorScheme
private var colorSchemeLoaderJob: Job? = null
private var drawableProvider: DynamicThemeProvider? = null private var drawableProvider: DynamicThemeProvider? = null
override fun getDrawableProvider(): DynamicThemeProvider { override fun getDrawableProvider(): DynamicThemeProvider {
if(drawableProvider == null) { if(drawableProvider == null) {
if(colorSchemeLoaderJob != null && !colorSchemeLoaderJob!!.isCompleted) {
runBlocking {
colorSchemeLoaderJob!!.join()
}
}
drawableProvider = BasicThemeProvider(this, activeColorScheme) drawableProvider = BasicThemeProvider(this, activeColorScheme)
} }
@ -310,7 +351,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
// recreate the keyboard if not in action window, if we are in action window then // recreate the keyboard if not in action window, if we are in action window then
// it'll be recreated when we exit // it'll be recreated when we exit
if(currWindowAction != null) recreateKeyboard() if(currWindowAction == null) recreateKeyboard()
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
setContent() setContent()
@ -344,12 +385,15 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
activeColorScheme = if(!DynamicColors.isDynamicColorAvailable()) { colorSchemeLoaderJob = lifecycleScope.launch {
DarkColorScheme var themeKey = this@LatinIME.getSetting(THEME_KEY, DynamicSystemTheme.key)
} else { var themeOption = ThemeOptions[themeKey]
val dCtx = DynamicColors.wrapContextIfAvailable(this) if(themeOption == null || !themeOption.available(this@LatinIME)) {
themeKey = VoiceInputTheme.key
themeOption = ThemeOptions[themeKey]!!
}
dynamicLightColorScheme(dCtx) activeColorScheme = themeOption.obtainColors(this@LatinIME)
} }
mSavedStateRegistryController.performRestore(null) mSavedStateRegistryController.performRestore(null)
@ -566,6 +610,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onWindowShown() { override fun onWindowShown() {
super.onWindowShown() super.onWindowShown()
latinIMELegacy.onWindowShown() latinIMELegacy.onWindowShown()
// TODO: Check here if the dynamic color scheme has changed, reset and rebuild if so
} }
override fun onWindowHidden() { override fun onWindowHidden() {
@ -685,7 +731,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
); );
} }
override fun updateTheme(newTheme: ColorScheme) { override fun updateTheme(newTheme: ThemeOption) {
updateDrawableProvider(newTheme) assert(newTheme.available(this))
updateDrawableProvider(newTheme.obtainColors(this))
lifecycleScope.launch {
withContext(Dispatchers.Default) {
setSetting(THEME_KEY, newTheme.key)
}
}
} }
} }

View File

@ -3,12 +3,13 @@ package org.futo.inputmethod.latin.uix
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import org.futo.inputmethod.latin.uix.theme.ThemeOption
interface KeyboardManagerForAction { interface KeyboardManagerForAction {
fun triggerSystemVoiceInput() fun triggerSystemVoiceInput()
fun updateTheme(newTheme: ColorScheme) fun updateTheme(newTheme: ThemeOption)
} }
interface ActionWindow { interface ActionWindow {

View File

@ -5,25 +5,33 @@ package org.futo.inputmethod.latin.uix
import android.os.Build import android.os.Build
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import org.futo.inputmethod.latin.uix.theme.ThemeOptionKeys
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
// TODO: For now, this calls CODE_SHORTCUT. In the future, we will want to // TODO: For now, this calls CODE_SHORTCUT. In the future, we will want to
@ -31,10 +39,27 @@ import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
val VoiceInputAction = Action( val VoiceInputAction = Action(
icon = R.drawable.mic_fill, icon = R.drawable.mic_fill,
name = "Voice Input", name = "Voice Input",
simplePressImpl = { //simplePressImpl = {
it.triggerSystemVoiceInput() // it.triggerSystemVoiceInput()
}, //},
windowImpl = null simplePressImpl = null,
windowImpl = object : ActionWindow {
@Composable
override fun windowName(): String {
return "Voice Input"
}
@Composable
override fun WindowContents(manager: KeyboardManagerForAction) {
Box(modifier = Modifier.fillMaxSize()) {
Icon(
painter = painterResource(id = R.drawable.mic_fill),
contentDescription = null,
modifier = Modifier.align(Alignment.Center).size(48.dp)
)
}
}
}
) )
val ThemeAction = Action( val ThemeAction = Action(
@ -50,238 +75,21 @@ val ThemeAction = Action(
@Composable @Composable
override fun WindowContents(manager: KeyboardManagerForAction) { override fun WindowContents(manager: KeyboardManagerForAction) {
val context = LocalContext.current val context = LocalContext.current
LazyColumn(modifier = Modifier.padding(8.dp, 0.dp).fillMaxWidth()) { LazyColumn(modifier = Modifier
item { .padding(8.dp, 0.dp)
.fillMaxWidth())
{
items(ThemeOptionKeys.count()) {
val key = ThemeOptionKeys[it]
val themeOption = ThemeOptions[key]
if(themeOption != null && themeOption.available(context)) {
Button(onClick = { Button(onClick = {
manager.updateTheme(DarkColorScheme)
}) {
Text("Default voice input theme")
}
}
item {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
Button(onClick = {
manager.updateTheme(dynamicLightColorScheme(context))
}) {
Text("Dynamic light color scheme")
}
Button(onClick = {
manager.updateTheme(dynamicDarkColorScheme(context))
}) {
Text("Dynamic dark color scheme")
}
}
}
item {
Button(onClick = {
val md_theme_light_primary = Color(0xFF6750A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFEADDFF)
val md_theme_light_onPrimaryContainer = Color(0xFF21005D)
val md_theme_light_secondary = Color(0xFF625B71)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFE8DEF8)
val md_theme_light_onSecondaryContainer = Color(0xFF1D192B)
val md_theme_light_tertiary = Color(0xFF7D5260)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4)
val md_theme_light_onTertiaryContainer = Color(0xFF31111D)
val md_theme_light_error = Color(0xFFB3261E)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_errorContainer = Color(0xFFF9DEDC)
val md_theme_light_onErrorContainer = Color(0xFF410E0B)
val md_theme_light_outline = Color(0xFF79747E)
val md_theme_light_background = Color(0xFFFFFBFE)
val md_theme_light_onBackground = Color(0xFF1C1B1F)
val md_theme_light_surface = Color(0xFFFFFBFE)
val md_theme_light_onSurface = Color(0xFF1C1B1F)
val md_theme_light_surfaceVariant = Color(0xFFE7E0EC)
val md_theme_light_onSurfaceVariant = Color(0xFF49454F)
val md_theme_light_inverseSurface = Color(0xFF313033)
val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4)
val md_theme_light_inversePrimary = Color(0xFFD0BCFF)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF6750A4)
val md_theme_light_outlineVariant = Color(0xFFCAC4D0)
val md_theme_light_scrim = Color(0xFF000000)
manager.updateTheme( manager.updateTheme(
lightColorScheme( themeOption
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
onError = md_theme_light_onError,
errorContainer = md_theme_light_errorContainer,
onErrorContainer = md_theme_light_onErrorContainer,
outline = md_theme_light_outline,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
inverseSurface = md_theme_light_inverseSurface,
inverseOnSurface = md_theme_light_inverseOnSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
) )
}) { }) {
Text("Some random light theme") Text(themeOption.name)
} }
Button(onClick = {
val md_theme_dark_primary = Color(0xFFD0BCFF)
val md_theme_dark_onPrimary = Color(0xFF381E72)
val md_theme_dark_primaryContainer = Color(0xFF4F378B)
val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)
val md_theme_dark_secondary = Color(0xFFCCC2DC)
val md_theme_dark_onSecondary = Color(0xFF332D41)
val md_theme_dark_secondaryContainer = Color(0xFF4A4458)
val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8)
val md_theme_dark_tertiary = Color(0xFFEFB8C8)
val md_theme_dark_onTertiary = Color(0xFF492532)
val md_theme_dark_tertiaryContainer = Color(0xFF633B48)
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4)
val md_theme_dark_error = Color(0xFFF2B8B5)
val md_theme_dark_onError = Color(0xFF601410)
val md_theme_dark_errorContainer = Color(0xFF8C1D18)
val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC)
val md_theme_dark_outline = Color(0xFF938F99)
val md_theme_dark_background = Color(0xFF1C1B1F)
val md_theme_dark_onBackground = Color(0xFFE6E1E5)
val md_theme_dark_surface = Color(0xFF1C1B1F)
val md_theme_dark_onSurface = Color(0xFFE6E1E5)
val md_theme_dark_surfaceVariant = Color(0xFF49454F)
val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0)
val md_theme_dark_inverseSurface = Color(0xFFE6E1E5)
val md_theme_dark_inverseOnSurface = Color(0xFF313033)
val md_theme_dark_inversePrimary = Color(0xFF6750A4)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFD0BCFF)
val md_theme_dark_outlineVariant = Color(0xFF49454F)
val md_theme_dark_scrim = Color(0xFF000000)
manager.updateTheme(
darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
onError = md_theme_dark_onError,
errorContainer = md_theme_dark_errorContainer,
onErrorContainer = md_theme_dark_onErrorContainer,
outline = md_theme_dark_outline,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
inverseSurface = md_theme_dark_inverseSurface,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
)
}) {
Text("Some random dark theme")
}
Button(onClick = {
val md_theme_dark_primary = Color(0xFFD0BCFF)
val md_theme_dark_onPrimary = Color(0xFF381E72)
val md_theme_dark_primaryContainer = Color(0xFF4F378B)
val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)
val md_theme_dark_secondary = Color(0xFFCCC2DC)
val md_theme_dark_onSecondary = Color(0xFF332D41)
val md_theme_dark_secondaryContainer = Color(0xFF4A4458)
val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8)
val md_theme_dark_tertiary = Color(0xFFEFB8C8)
val md_theme_dark_onTertiary = Color(0xFF492532)
val md_theme_dark_tertiaryContainer = Color(0xFF633B48)
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4)
val md_theme_dark_error = Color(0xFFF2B8B5)
val md_theme_dark_onError = Color(0xFF601410)
val md_theme_dark_errorContainer = Color(0xFF8C1D18)
val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC)
val md_theme_dark_outline = Color(0xFF938F99)
val md_theme_dark_background = Color(0xFF000000)
val md_theme_dark_onBackground = Color(0xFFE6E1E5)
val md_theme_dark_surface = Color(0xFF000000)
val md_theme_dark_onSurface = Color(0xFFE6E1E5)
val md_theme_dark_surfaceVariant = Color(0xFF49454F)
val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0)
val md_theme_dark_inverseSurface = Color(0xFFE6E1E5)
val md_theme_dark_inverseOnSurface = Color(0xFF313033)
val md_theme_dark_inversePrimary = Color(0xFF6750A4)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFD0BCFF)
val md_theme_dark_outlineVariant = Color(0xFF49454F)
val md_theme_dark_scrim = Color(0xFF000000)
manager.updateTheme(
darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
onError = md_theme_dark_onError,
errorContainer = md_theme_dark_errorContainer,
onErrorContainer = md_theme_dark_onErrorContainer,
outline = md_theme_dark_outline,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
inverseSurface = md_theme_dark_inverseSurface,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
)
}) {
Text("AMOLED dark")
} }
} }
} }

View File

@ -0,0 +1,37 @@
package org.futo.inputmethod.latin.uix.theme
import android.content.Context
import androidx.compose.material3.ColorScheme
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.DynamicDarkTheme
import org.futo.inputmethod.latin.uix.theme.presets.DynamicLightTheme
import org.futo.inputmethod.latin.uix.theme.presets.DynamicSystemTheme
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
data class ThemeOption(
val key: String,
val name: String, // TODO: @StringRes Int
val available: (Context) -> Boolean,
val obtainColors: (Context) -> ColorScheme,
)
val ThemeOptions = hashMapOf(
DynamicSystemTheme.key to DynamicSystemTheme,
DynamicDarkTheme.key to DynamicDarkTheme,
DynamicLightTheme.key to DynamicLightTheme,
ClassicMaterialDark.key to ClassicMaterialDark,
VoiceInputTheme.key to VoiceInputTheme,
AMOLEDDarkPurple.key to AMOLEDDarkPurple,
)
val ThemeOptionKeys = arrayOf(
DynamicSystemTheme.key,
DynamicDarkTheme.key,
DynamicLightTheme.key,
ClassicMaterialDark.key,
VoiceInputTheme.key,
AMOLEDDarkPurple.key,
)

View File

@ -0,0 +1,72 @@
package org.futo.inputmethod.latin.uix.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.ui.graphics.Color
import org.futo.inputmethod.latin.uix.theme.ThemeOption
private val md_theme_dark_primary = Color(0xFFD0BCFF)
private val md_theme_dark_onPrimary = Color(0xFF381E72)
private val md_theme_dark_primaryContainer = Color(0xFF4F378B)
private val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)
private val md_theme_dark_secondary = Color(0xFFCCC2DC)
private val md_theme_dark_onSecondary = Color(0xFF332D41)
private val md_theme_dark_secondaryContainer = Color(0xFF4A4458)
private val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8)
private val md_theme_dark_tertiary = Color(0xFFEFB8C8)
private val md_theme_dark_onTertiary = Color(0xFF492532)
private val md_theme_dark_tertiaryContainer = Color(0xFF633B48)
private val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4)
private val md_theme_dark_error = Color(0xFFF2B8B5)
private val md_theme_dark_onError = Color(0xFF601410)
private val md_theme_dark_errorContainer = Color(0xFF8C1D18)
private val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC)
private val md_theme_dark_outline = Color(0xFF938F99)
private val md_theme_dark_background = Color(0xFF000000)
private val md_theme_dark_onBackground = Color(0xFFE6E1E5)
private val md_theme_dark_surface = Color(0xFF000000)
private val md_theme_dark_onSurface = Color(0xFFE6E1E5)
private val md_theme_dark_surfaceVariant = Color(0xFF49454F)
private val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0)
private val md_theme_dark_inverseSurface = Color(0xFFE6E1E5)
private val md_theme_dark_inverseOnSurface = Color(0xFF313033)
private val md_theme_dark_inversePrimary = Color(0xFF6750A4)
private val md_theme_dark_shadow = Color(0xFF000000)
private val md_theme_dark_surfaceTint = Color(0xFFD0BCFF)
private val md_theme_dark_outlineVariant = Color(0xFF49454F)
private val md_theme_dark_scrim = Color(0xFF000000)
private val colorScheme = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
onError = md_theme_dark_onError,
errorContainer = md_theme_dark_errorContainer,
onErrorContainer = md_theme_dark_onErrorContainer,
outline = md_theme_dark_outline,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
inverseSurface = md_theme_dark_inverseSurface,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
val AMOLEDDarkPurple = ThemeOption("AMOLEDDarkPurple", "AMOLED Dark Purple", { true }) {
colorScheme
}

View File

@ -0,0 +1,121 @@
package org.futo.inputmethod.latin.uix.theme.presets
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.futo.inputmethod.latin.uix.theme.ThemeOption
private val md_theme_dark_primary = Color(0xFF80cbc4)
private val md_theme_dark_onPrimary = Color(0xFFFFFFFF)
private val md_theme_dark_primaryContainer = Color(0xFF00504B)
private val md_theme_dark_onPrimaryContainer = Color(0xFF71F7ED)
private val md_theme_dark_secondary = Color(0xFF80cbc4)
private val md_theme_dark_onSecondary = Color(0xFFFFFFFF)
private val md_theme_dark_secondaryContainer = Color(0xFF34434B)
private val md_theme_dark_onSecondaryContainer = Color(0xFFFFFFFF)
private val md_theme_dark_tertiary = Color(0xFF3582A2)
private val md_theme_dark_onTertiary = Color(0xFFFFFFFF)
private val md_theme_dark_tertiaryContainer = Color(0xFF004D64)
private val md_theme_dark_onTertiaryContainer = Color(0xFFBDE9FF)
private val md_theme_dark_error = Color(0xFFFFB4AB)
private val md_theme_dark_errorContainer = Color(0xFF93000A)
private val md_theme_dark_onError = Color(0xFF690005)
private val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
private val md_theme_dark_background = Color(0xFF21272b)
private val md_theme_dark_onBackground = Color(0xFFFFFFFF)
private val md_theme_dark_surface = Color(0xFF263238)
private val md_theme_dark_onSurface = Color(0xFFFFFFFF)
private val md_theme_dark_surfaceVariant = Color(0xFF3F4947)
private val md_theme_dark_onSurfaceVariant = Color(0xFFBEC9C7)
private val md_theme_dark_outline = Color(0xFF899391)
private val md_theme_dark_inverseOnSurface = Color(0xFF001F2A)
private val md_theme_dark_inverseSurface = Color(0xFFBFE9FF)
private val md_theme_dark_inversePrimary = Color(0xFF006A64)
private val md_theme_dark_shadow = Color(0xFF000000)
private val md_theme_dark_surfaceTint = Color(0xFF4FDBD1)
private val md_theme_dark_outlineVariant = Color(0xFF3F4947)
private val md_theme_dark_scrim = Color(0xFF000000)
private val colorScheme = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
onError = md_theme_dark_onError,
errorContainer = md_theme_dark_errorContainer,
onErrorContainer = md_theme_dark_onErrorContainer,
outline = md_theme_dark_outline,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
inverseSurface = md_theme_dark_inverseSurface,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
val ClassicMaterialDark = ThemeOption("ClassicMaterialDark", "AOSP Material Dark", { true }) {
colorScheme
}
@Composable
@Preview
private fun PreviewTheme() {
MaterialTheme(colorScheme) {
Column(modifier = Modifier.fillMaxSize()) {
Spacer(modifier = Modifier.weight(1.5f))
Surface(color = MaterialTheme.colorScheme.background, modifier = Modifier
.fillMaxWidth()
.height(48.dp)) {
}
Surface(color = MaterialTheme.colorScheme.surface, modifier = Modifier
.fillMaxWidth()
.weight(1.0f)) {
Box(modifier = Modifier.padding(16.dp)) {
Surface(
color = MaterialTheme.colorScheme.primary, modifier = Modifier
.align(
Alignment.BottomEnd
)
.height(32.dp)
.width(48.dp),
shape = RoundedCornerShape(8.dp)
) {
}
}
}
}
}
}

View File

@ -0,0 +1,34 @@
package org.futo.inputmethod.latin.uix.theme.presets
import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import org.futo.inputmethod.latin.uix.theme.ThemeOption
val DynamicSystemTheme = ThemeOption("DynamicSystem", "Dynamic System", { Build.VERSION.SDK_INT >= Build.VERSION_CODES.S }) {
val uiModeManager = it.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
when (uiModeManager.nightMode) {
UiModeManager.MODE_NIGHT_YES -> dynamicDarkColorScheme(it)
UiModeManager.MODE_NIGHT_NO -> dynamicLightColorScheme(it)
UiModeManager.MODE_NIGHT_AUTO -> {
val currentNightMode = it.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
if(currentNightMode == Configuration.UI_MODE_NIGHT_NO) {
dynamicLightColorScheme(it)
} else {
dynamicDarkColorScheme(it)
}
}
else -> dynamicDarkColorScheme(it)
}
}
val DynamicDarkTheme = ThemeOption("DynamicDark", "Dynamic Dark", { Build.VERSION.SDK_INT >= Build.VERSION_CODES.S }) {
dynamicDarkColorScheme(it)
}
val DynamicLightTheme = ThemeOption("DynamicLight", "Dynamic Light", { Build.VERSION.SDK_INT >= Build.VERSION_CODES.S }) {
dynamicLightColorScheme(it)
}

View File

@ -0,0 +1,8 @@
package org.futo.inputmethod.latin.uix.theme.presets
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import org.futo.inputmethod.latin.uix.theme.ThemeOption
val VoiceInputTheme = ThemeOption("VoiceInputTheme", "Voice Input Theme", { true }) {
DarkColorScheme
}