Custom color schemes v2 - Part 1
Add data structures and theme editor
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,50 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = sdk.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = sdk.versions.minSdk.get().toInt()
|
||||
targetSdk = sdk.versions.targetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
namespace = "de.mm20.launcher2.themes"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bundles.kotlin)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":core:base"))
|
||||
implementation(project(":core:database"))
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":libs:material-color-utilities"))
|
||||
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.kts.kts.kts.kts.kts.kts.kts.kts.kts.kts.kts.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1 @@
|
||||
<manifest />
|
||||
@@ -0,0 +1,84 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
val DefaultThemeId = UUID(0L, 0L)
|
||||
|
||||
val DefaultLightColorScheme = ColorScheme<Color>(
|
||||
primary = ColorRef(CorePaletteColor.Primary, 40),
|
||||
onPrimary = ColorRef(CorePaletteColor.Primary, 100),
|
||||
primaryContainer = ColorRef(CorePaletteColor.Primary, 90),
|
||||
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 10),
|
||||
secondary = ColorRef(CorePaletteColor.Secondary, 40),
|
||||
onSecondary = ColorRef(CorePaletteColor.Secondary, 100),
|
||||
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
|
||||
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 10),
|
||||
tertiary = ColorRef(CorePaletteColor.Tertiary, 40),
|
||||
onTertiary = ColorRef(CorePaletteColor.Tertiary, 100),
|
||||
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
|
||||
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 10),
|
||||
error = ColorRef(CorePaletteColor.Error, 40),
|
||||
onError = ColorRef(CorePaletteColor.Error, 100),
|
||||
errorContainer = ColorRef(CorePaletteColor.Error, 90),
|
||||
onErrorContainer = ColorRef(CorePaletteColor.Error, 10),
|
||||
surfaceDim = ColorRef(CorePaletteColor.Neutral, 87),
|
||||
surface = ColorRef(CorePaletteColor.Neutral, 98),
|
||||
surfaceBright = ColorRef(CorePaletteColor.Neutral, 98),
|
||||
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 100),
|
||||
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 96),
|
||||
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 94),
|
||||
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 92),
|
||||
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 90),
|
||||
onSurface = ColorRef(CorePaletteColor.Neutral, 10),
|
||||
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
|
||||
outline = ColorRef(CorePaletteColor.NeutralVariant, 50),
|
||||
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
|
||||
inverseSurface = ColorRef(CorePaletteColor.Neutral, 20),
|
||||
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 95),
|
||||
inversePrimary = ColorRef(CorePaletteColor.Primary, 80),
|
||||
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 90),
|
||||
surfaceTint = ColorRef(CorePaletteColor.Primary, 40),
|
||||
background = ColorRef(CorePaletteColor.Neutral, 98),
|
||||
onBackground = ColorRef(CorePaletteColor.Neutral, 10),
|
||||
scrim = ColorRef(CorePaletteColor.Neutral, 0),
|
||||
)
|
||||
|
||||
val DefaultDarkColorScheme = ColorScheme<Color>(
|
||||
primary = ColorRef(CorePaletteColor.Primary, 80),
|
||||
onPrimary = ColorRef(CorePaletteColor.Primary, 20),
|
||||
primaryContainer = ColorRef(CorePaletteColor.Primary, 30),
|
||||
onPrimaryContainer = ColorRef(CorePaletteColor.Primary, 90),
|
||||
secondary = ColorRef(CorePaletteColor.Secondary, 80),
|
||||
onSecondary = ColorRef(CorePaletteColor.Secondary, 20),
|
||||
secondaryContainer = ColorRef(CorePaletteColor.Secondary, 30),
|
||||
onSecondaryContainer = ColorRef(CorePaletteColor.Secondary, 90),
|
||||
tertiary = ColorRef(CorePaletteColor.Tertiary, 80),
|
||||
onTertiary = ColorRef(CorePaletteColor.Tertiary, 20),
|
||||
tertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 30),
|
||||
onTertiaryContainer = ColorRef(CorePaletteColor.Tertiary, 90),
|
||||
error = ColorRef(CorePaletteColor.Error, 80),
|
||||
onError = ColorRef(CorePaletteColor.Error, 20),
|
||||
errorContainer = ColorRef(CorePaletteColor.Error, 30),
|
||||
onErrorContainer = ColorRef(CorePaletteColor.Error, 90),
|
||||
surfaceDim = ColorRef(CorePaletteColor.Neutral, 6),
|
||||
surface = ColorRef(CorePaletteColor.Neutral, 6),
|
||||
surfaceBright = ColorRef(CorePaletteColor.Neutral, 24),
|
||||
surfaceContainerLowest = ColorRef(CorePaletteColor.Neutral, 4),
|
||||
surfaceContainerLow = ColorRef(CorePaletteColor.Neutral, 10),
|
||||
surfaceContainer = ColorRef(CorePaletteColor.Neutral, 12),
|
||||
surfaceContainerHigh = ColorRef(CorePaletteColor.Neutral, 17),
|
||||
surfaceContainerHighest = ColorRef(CorePaletteColor.Neutral, 22),
|
||||
onSurface = ColorRef(CorePaletteColor.Neutral, 90),
|
||||
onSurfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 80),
|
||||
outline = ColorRef(CorePaletteColor.NeutralVariant, 60),
|
||||
outlineVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
|
||||
inverseSurface = ColorRef(CorePaletteColor.Neutral, 98),
|
||||
inverseOnSurface = ColorRef(CorePaletteColor.Neutral, 10),
|
||||
inversePrimary = ColorRef(CorePaletteColor.Primary, 40),
|
||||
surfaceVariant = ColorRef(CorePaletteColor.NeutralVariant, 30),
|
||||
surfaceTint = ColorRef(CorePaletteColor.Primary, 80),
|
||||
background = ColorRef(CorePaletteColor.Neutral, 6),
|
||||
onBackground = ColorRef(CorePaletteColor.Neutral, 90),
|
||||
scrim = ColorRef(CorePaletteColor.Neutral, 0),
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val themesModule = module {
|
||||
factory { ThemeRepository(get()) }
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import hct.Hct
|
||||
import java.util.UUID
|
||||
|
||||
enum class CorePaletteColor {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Neutral,
|
||||
NeutralVariant,
|
||||
Error;
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
Primary -> "p"
|
||||
Secondary -> "s"
|
||||
Tertiary -> "t"
|
||||
Neutral -> "n"
|
||||
NeutralVariant -> "nv"
|
||||
Error -> "e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Color
|
||||
|
||||
data class ColorRef(
|
||||
val color: CorePaletteColor,
|
||||
val tone: Int,
|
||||
) : Color {
|
||||
override fun toString(): String {
|
||||
return "\$${color.name}.$tone"
|
||||
}
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class StaticColor(val color: Int) : Color {
|
||||
override fun toString(): String {
|
||||
return "#${color.toString(16).padStart(6, '0')}"
|
||||
}
|
||||
}
|
||||
|
||||
data class CorePalette<out T : Int?>(
|
||||
val primary: T,
|
||||
val secondary: T,
|
||||
val tertiary: T,
|
||||
val neutral: T,
|
||||
val neutralVariant: T,
|
||||
val error: T,
|
||||
)
|
||||
|
||||
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
||||
|
||||
typealias FullCorePalette = CorePalette<Int>
|
||||
typealias PartialCorePalette = CorePalette<Int?>
|
||||
|
||||
data class ColorScheme<out T: Color?>(
|
||||
val primary: T,
|
||||
val onPrimary: T,
|
||||
val primaryContainer: T,
|
||||
val onPrimaryContainer: T,
|
||||
val secondary: T,
|
||||
val onSecondary: T,
|
||||
val secondaryContainer: T,
|
||||
val onSecondaryContainer: T,
|
||||
val tertiary: T,
|
||||
val onTertiary: T,
|
||||
val tertiaryContainer: T,
|
||||
val onTertiaryContainer: T,
|
||||
val error: T,
|
||||
val onError: T,
|
||||
val errorContainer: T,
|
||||
val onErrorContainer: T,
|
||||
val surface: T,
|
||||
val onSurface: T,
|
||||
val onSurfaceVariant: T,
|
||||
val outline: T,
|
||||
val outlineVariant: T,
|
||||
val inverseSurface: T,
|
||||
val inverseOnSurface: T,
|
||||
val inversePrimary: T,
|
||||
val surfaceDim: T,
|
||||
val surfaceBright: T,
|
||||
val surfaceContainerLowest: T,
|
||||
val surfaceContainerLow: T,
|
||||
val surfaceContainer: T,
|
||||
val surfaceContainerHigh: T,
|
||||
val surfaceContainerHighest: T,
|
||||
|
||||
val background: T,
|
||||
val onBackground: T,
|
||||
val surfaceTint: T,
|
||||
val scrim: T,
|
||||
val surfaceVariant: T,
|
||||
)
|
||||
|
||||
typealias FullColorScheme = ColorScheme<Color>
|
||||
typealias PartialColorScheme = ColorScheme<Color?>
|
||||
|
||||
data class Theme(
|
||||
val id: UUID,
|
||||
val builtIn: Boolean = false,
|
||||
val name: String,
|
||||
val corePalette: PartialCorePalette = EmptyCorePalette,
|
||||
val lightColorScheme: PartialColorScheme = DefaultLightColorScheme,
|
||||
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme,
|
||||
)
|
||||
|
||||
fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
|
||||
return when (color) {
|
||||
CorePaletteColor.Primary -> primary
|
||||
CorePaletteColor.Secondary -> secondary
|
||||
CorePaletteColor.Tertiary -> tertiary
|
||||
CorePaletteColor.Neutral -> neutral
|
||||
CorePaletteColor.NeutralVariant -> neutralVariant
|
||||
CorePaletteColor.Error -> error
|
||||
}
|
||||
}
|
||||
|
||||
fun Color.get(corePalette: FullCorePalette): Int {
|
||||
return when (this) {
|
||||
is StaticColor -> color
|
||||
is ColorRef -> {
|
||||
corePalette.get(this.color).atTone(this.tone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.atTone(tone: Int): Int {
|
||||
return Hct.fromInt(this).apply {
|
||||
setTone(tone.toDouble())
|
||||
}.toInt()
|
||||
}
|
||||
|
||||
fun PartialCorePalette.merge(other: FullCorePalette): FullCorePalette {
|
||||
return CorePalette(
|
||||
primary = this.primary ?: other.primary,
|
||||
secondary = this.secondary ?: other.secondary,
|
||||
tertiary = this.tertiary ?: other.tertiary,
|
||||
neutral = this.neutral ?: other.neutral,
|
||||
neutralVariant = this.neutralVariant ?: other.neutralVariant,
|
||||
error = this.error ?: other.error,
|
||||
)
|
||||
}
|
||||
|
||||
fun PartialColorScheme.merge(other: FullColorScheme): FullColorScheme {
|
||||
return ColorScheme(
|
||||
primary = this.primary ?: other.primary,
|
||||
onPrimary = this.onPrimary ?: other.onPrimary,
|
||||
primaryContainer = this.primaryContainer ?: other.primaryContainer,
|
||||
onPrimaryContainer = this.onPrimaryContainer ?: other.onPrimaryContainer,
|
||||
secondary = this.secondary ?: other.secondary,
|
||||
onSecondary = this.onSecondary ?: other.onSecondary,
|
||||
secondaryContainer = this.secondaryContainer ?: other.secondaryContainer,
|
||||
onSecondaryContainer = this.onSecondaryContainer ?: other.onSecondaryContainer,
|
||||
tertiary = this.tertiary ?: other.tertiary,
|
||||
onTertiary = this.onTertiary ?: other.onTertiary,
|
||||
tertiaryContainer = this.tertiaryContainer ?: other.tertiaryContainer,
|
||||
onTertiaryContainer = this.onTertiaryContainer ?: other.onTertiaryContainer,
|
||||
error = this.error ?: other.error,
|
||||
onError = this.onError ?: other.onError,
|
||||
errorContainer = this.errorContainer ?: other.errorContainer,
|
||||
onErrorContainer = this.onErrorContainer ?: other.onErrorContainer,
|
||||
surfaceDim = this.surfaceDim ?: other.surfaceDim,
|
||||
surface = this.surface ?: other.surface,
|
||||
surfaceBright = this.surfaceBright ?: other.surfaceBright,
|
||||
surfaceContainerLowest = this.surfaceContainerLowest ?: other.surfaceContainerLowest,
|
||||
surfaceContainerLow = this.surfaceContainerLow ?: other.surfaceContainerLow,
|
||||
surfaceContainer = this.surfaceContainer ?: other.surfaceContainer,
|
||||
surfaceContainerHigh = this.surfaceContainerHigh ?: other.surfaceContainerHigh,
|
||||
surfaceContainerHighest = this.surfaceContainerHighest ?: other.surfaceContainerHighest,
|
||||
onSurface = this.onSurface ?: other.onSurface,
|
||||
onSurfaceVariant = this.onSurfaceVariant ?: other.onSurfaceVariant,
|
||||
outline = this.outline ?: other.outline,
|
||||
outlineVariant = this.outlineVariant ?: other.outlineVariant,
|
||||
inverseSurface = this.inverseSurface ?: other.inverseSurface,
|
||||
inverseOnSurface = this.inverseOnSurface ?: other.inverseOnSurface,
|
||||
inversePrimary = this.inversePrimary ?: other.inversePrimary,
|
||||
surfaceVariant = this.surfaceVariant ?: other.surfaceVariant,
|
||||
scrim = this.scrim ?: other.scrim,
|
||||
onBackground = this.onBackground ?: other.onBackground,
|
||||
background = this.background ?: other.background,
|
||||
surfaceTint = this.surfaceTint ?: other.surfaceTint,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.UUID
|
||||
|
||||
class ThemeRepository(
|
||||
private val context: Context,
|
||||
) {
|
||||
private val customTheme = MutableStateFlow(Theme(
|
||||
id = UUID.randomUUID(),
|
||||
builtIn = false,
|
||||
name = "Custom",
|
||||
corePalette = EmptyCorePalette,
|
||||
lightColorScheme = DefaultLightColorScheme,
|
||||
darkColorScheme = DefaultDarkColorScheme,
|
||||
))
|
||||
|
||||
fun getThemes(): Flow<List<Theme>> {
|
||||
return flowOf(getBuiltInThemes()).combine(customTheme) {
|
||||
builtIn, custom ->
|
||||
builtIn + custom
|
||||
}
|
||||
}
|
||||
|
||||
fun getTheme(id: UUID): Flow<Theme?> {
|
||||
if (id == DefaultThemeId) return flowOf(getDefaultTheme())
|
||||
return customTheme
|
||||
}
|
||||
|
||||
fun createTheme(theme: Theme) {
|
||||
}
|
||||
|
||||
fun updateTheme(theme: Theme) {
|
||||
Log.d("MM20", "updateTheme: $theme")
|
||||
customTheme.value = theme
|
||||
}
|
||||
|
||||
fun getThemeOrDefault(id: UUID): Flow<Theme> {
|
||||
return getTheme(id).map { it ?: getDefaultTheme() }
|
||||
}
|
||||
|
||||
private fun getBuiltInThemes(): List<Theme> {
|
||||
return listOf(
|
||||
getDefaultTheme(),
|
||||
)
|
||||
}
|
||||
|
||||
fun getDefaultTheme(): Theme {
|
||||
return Theme(
|
||||
id = DefaultThemeId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_colors_default),
|
||||
corePalette = EmptyCorePalette,
|
||||
lightColorScheme = DefaultLightColorScheme,
|
||||
darkColorScheme = DefaultDarkColorScheme,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user