Custom color schemes v2 - Part 3

Store custom themes in database
This commit is contained in:
MM20 2023-08-22 22:08:04 +02:00
parent 4e681234fd
commit 70f658f9f3
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
3 changed files with 229 additions and 10 deletions

View File

@ -3,5 +3,5 @@ package de.mm20.launcher2.themes
import org.koin.dsl.module
val themesModule = module {
factory { ThemeRepository(get()) }
factory { ThemeRepository(get(), get()) }
}

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.themes
import de.mm20.launcher2.database.entities.ThemeEntity
import hct.Hct
import java.util.UUID
@ -23,14 +24,42 @@ enum class CorePaletteColor {
}
}
fun CorePaletteColor(color: String): CorePaletteColor? {
return when (color) {
"p" -> CorePaletteColor.Primary
"s" -> CorePaletteColor.Secondary
"t" -> CorePaletteColor.Tertiary
"n" -> CorePaletteColor.Neutral
"nv" -> CorePaletteColor.NeutralVariant
"e" -> CorePaletteColor.Error
else -> null
}
}
sealed interface Color
internal fun Color(string: String?): Color? {
if (string == null) return null
if (string.startsWith("#")) {
return StaticColor(string.substring(1).toLongOrNull(16)?.toInt() ?: return null)
}
if (string.startsWith("$")) {
val parts = string.substring(1).split(".").takeIf { it.size == 2 } ?: return null
val color = CorePaletteColor(parts[0]) ?: return null
return ColorRef(
color = color,
tone = parts[1].toIntOrNull() ?: return null,
)
}
return null
}
data class ColorRef(
val color: CorePaletteColor,
val tone: Int,
) : Color {
override fun toString(): String {
return "\$${color.name}.$tone"
return "\$${color.toString()}.$tone"
}
}
@ -55,7 +84,7 @@ val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
typealias FullCorePalette = CorePalette<Int>
typealias PartialCorePalette = CorePalette<Int?>
data class ColorScheme<out T: Color?>(
data class ColorScheme<out T : Color?>(
val primary: T,
val onPrimary: T,
val primaryContainer: T,
@ -105,7 +134,186 @@ data class Theme(
val corePalette: PartialCorePalette = EmptyCorePalette,
val lightColorScheme: PartialColorScheme = DefaultLightColorScheme,
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme,
)
) {
constructor(entity: ThemeEntity) : this(
id = entity.id,
builtIn = false,
name = entity.name,
corePalette = CorePalette(
primary = entity.corePaletteA1,
secondary = entity.corePaletteA2,
tertiary = entity.corePaletteA3,
neutral = entity.corePaletteN1,
neutralVariant = entity.corePaletteN2,
error = entity.corePaletteE,
),
lightColorScheme = ColorScheme(
primary = Color(entity.lightPrimary),
onPrimary = Color(entity.lightOnPrimary),
primaryContainer = Color(entity.lightPrimaryContainer),
onPrimaryContainer = Color(entity.lightOnPrimaryContainer),
secondary = Color(entity.lightSecondary),
onSecondary = Color(entity.lightOnSecondary),
secondaryContainer = Color(entity.lightSecondaryContainer),
onSecondaryContainer = Color(entity.lightOnSecondaryContainer),
tertiary = Color(entity.lightTertiary),
onTertiary = Color(entity.lightOnTertiary),
tertiaryContainer = Color(entity.lightTertiaryContainer),
onTertiaryContainer = Color(entity.lightOnTertiaryContainer),
error = Color(entity.lightError),
onError = Color(entity.lightOnError),
errorContainer = Color(entity.lightErrorContainer),
onErrorContainer = Color(entity.lightOnErrorContainer),
surface = Color(entity.lightSurface),
onSurface = Color(entity.lightOnSurface),
onSurfaceVariant = Color(entity.lightOnSurfaceVariant),
outline = Color(entity.lightOutline),
outlineVariant = Color(entity.lightOutlineVariant),
inverseSurface = Color(entity.lightInverseSurface),
inverseOnSurface = Color(entity.lightInverseOnSurface),
inversePrimary = Color(entity.lightInversePrimary),
surfaceDim = Color(entity.lightSurfaceDim),
surfaceBright = Color(entity.lightSurfaceBright),
surfaceContainerLowest = Color(entity.lightSurfaceContainerLowest),
surfaceContainerLow = Color(entity.lightSurfaceContainerLow),
surfaceContainer = Color(entity.lightSurfaceContainer),
surfaceContainerHigh = Color(entity.lightSurfaceContainerHigh),
surfaceContainerHighest = Color(entity.lightSurfaceContainerHighest),
background = Color(entity.lightBackground),
onBackground = Color(entity.lightOnBackground),
surfaceTint = Color(entity.lightSurfaceTint),
scrim = Color(entity.lightScrim),
surfaceVariant = Color(entity.lightSurfaceVariant),
),
darkColorScheme = ColorScheme(
primary = Color(entity.darkPrimary),
onPrimary = Color(entity.darkOnPrimary),
primaryContainer = Color(entity.darkPrimaryContainer),
onPrimaryContainer = Color(entity.darkOnPrimaryContainer),
secondary = Color(entity.darkSecondary),
onSecondary = Color(entity.darkOnSecondary),
secondaryContainer = Color(entity.darkSecondaryContainer),
onSecondaryContainer = Color(entity.darkOnSecondaryContainer),
tertiary = Color(entity.darkTertiary),
onTertiary = Color(entity.darkOnTertiary),
tertiaryContainer = Color(entity.darkTertiaryContainer),
onTertiaryContainer = Color(entity.darkOnTertiaryContainer),
error = Color(entity.darkError),
onError = Color(entity.darkOnError),
errorContainer = Color(entity.darkErrorContainer),
onErrorContainer = Color(entity.darkOnErrorContainer),
surface = Color(entity.darkSurface),
onSurface = Color(entity.darkOnSurface),
onSurfaceVariant = Color(entity.darkOnSurfaceVariant),
outline = Color(entity.darkOutline),
outlineVariant = Color(entity.darkOutlineVariant),
inverseSurface = Color(entity.darkInverseSurface),
inverseOnSurface = Color(entity.darkInverseOnSurface),
inversePrimary = Color(entity.darkInversePrimary),
surfaceDim = Color(entity.darkSurfaceDim),
surfaceBright = Color(entity.darkSurfaceBright),
surfaceContainerLowest = Color(entity.darkSurfaceContainerLowest),
surfaceContainerLow = Color(entity.darkSurfaceContainerLow),
surfaceContainer = Color(entity.darkSurfaceContainer),
surfaceContainerHigh = Color(entity.darkSurfaceContainerHigh),
surfaceContainerHighest = Color(entity.darkSurfaceContainerHighest),
background = Color(entity.darkBackground),
onBackground = Color(entity.darkOnBackground),
surfaceTint = Color(entity.darkSurfaceTint),
scrim = Color(entity.darkScrim),
surfaceVariant = Color(entity.darkSurfaceVariant),
),
)
internal fun toEntity(): ThemeEntity {
return ThemeEntity(
id = id,
name = name,
corePaletteA1 = corePalette.primary,
corePaletteA2 = corePalette.secondary,
corePaletteA3 = corePalette.tertiary,
corePaletteN1 = corePalette.neutral,
corePaletteN2 = corePalette.neutralVariant,
corePaletteE = corePalette.error,
lightPrimary = lightColorScheme.primary?.toString(),
lightOnPrimary = lightColorScheme.onPrimary?.toString(),
lightPrimaryContainer = lightColorScheme.primaryContainer?.toString(),
lightOnPrimaryContainer = lightColorScheme.onPrimaryContainer?.toString(),
lightSecondary = lightColorScheme.secondary?.toString(),
lightOnSecondary = lightColorScheme.onSecondary?.toString(),
lightSecondaryContainer = lightColorScheme.secondaryContainer?.toString(),
lightOnSecondaryContainer = lightColorScheme.onSecondaryContainer?.toString(),
lightTertiary = lightColorScheme.tertiary?.toString(),
lightOnTertiary = lightColorScheme.onTertiary?.toString(),
lightTertiaryContainer = lightColorScheme.tertiaryContainer?.toString(),
lightOnTertiaryContainer = lightColorScheme.onTertiaryContainer?.toString(),
lightError = lightColorScheme.error?.toString(),
lightOnError = lightColorScheme.onError?.toString(),
lightErrorContainer = lightColorScheme.errorContainer?.toString(),
lightOnErrorContainer = lightColorScheme.onErrorContainer?.toString(),
lightSurface = lightColorScheme.surface?.toString(),
lightOnSurface = lightColorScheme.onSurface?.toString(),
lightOnSurfaceVariant = lightColorScheme.onSurfaceVariant?.toString(),
lightOutline = lightColorScheme.outline?.toString(),
lightOutlineVariant = lightColorScheme.outlineVariant?.toString(),
lightInverseSurface = lightColorScheme.inverseSurface?.toString(),
lightInverseOnSurface = lightColorScheme.inverseOnSurface?.toString(),
lightInversePrimary = lightColorScheme.inversePrimary?.toString(),
lightSurfaceDim = lightColorScheme.surfaceDim?.toString(),
lightSurfaceBright = lightColorScheme.surfaceBright?.toString(),
lightSurfaceContainerLowest = lightColorScheme.surfaceContainerLowest?.toString(),
lightSurfaceContainerLow = lightColorScheme.surfaceContainerLow?.toString(),
lightSurfaceContainer = lightColorScheme.surfaceContainer?.toString(),
lightSurfaceContainerHigh = lightColorScheme.surfaceContainerHigh?.toString(),
lightSurfaceContainerHighest = lightColorScheme.surfaceContainerHighest?.toString(),
lightBackground = lightColorScheme.background?.toString(),
lightOnBackground = lightColorScheme.onBackground?.toString(),
lightSurfaceTint = lightColorScheme.surfaceTint?.toString(),
lightScrim = lightColorScheme.scrim?.toString(),
lightSurfaceVariant = lightColorScheme.surfaceVariant?.toString(),
darkPrimary = darkColorScheme.primary?.toString(),
darkOnPrimary = darkColorScheme.onPrimary?.toString(),
darkPrimaryContainer = darkColorScheme.primaryContainer?.toString(),
darkOnPrimaryContainer = darkColorScheme.onPrimaryContainer?.toString(),
darkSecondary = darkColorScheme.secondary?.toString(),
darkOnSecondary = darkColorScheme.onSecondary?.toString(),
darkSecondaryContainer = darkColorScheme.secondaryContainer?.toString(),
darkOnSecondaryContainer = darkColorScheme.onSecondaryContainer?.toString(),
darkTertiary = darkColorScheme.tertiary?.toString(),
darkOnTertiary = darkColorScheme.onTertiary?.toString(),
darkTertiaryContainer = darkColorScheme.tertiaryContainer?.toString(),
darkOnTertiaryContainer = darkColorScheme.onTertiaryContainer?.toString(),
darkError = darkColorScheme.error?.toString(),
darkOnError = darkColorScheme.onError?.toString(),
darkErrorContainer = darkColorScheme.errorContainer?.toString(),
darkOnErrorContainer = darkColorScheme.onErrorContainer?.toString(),
darkSurface = darkColorScheme.surface?.toString(),
darkOnSurface = darkColorScheme.onSurface?.toString(),
darkOnSurfaceVariant = darkColorScheme.onSurfaceVariant?.toString(),
darkOutline = darkColorScheme.outline?.toString(),
darkOutlineVariant = darkColorScheme.outlineVariant?.toString(),
darkInverseSurface = darkColorScheme.inverseSurface?.toString(),
darkInverseOnSurface = darkColorScheme.inverseOnSurface?.toString(),
darkInversePrimary = darkColorScheme.inversePrimary?.toString(),
darkSurfaceDim = darkColorScheme.surfaceDim?.toString(),
darkSurfaceBright = darkColorScheme.surfaceBright?.toString(),
darkSurfaceContainerLowest = darkColorScheme.surfaceContainerLowest?.toString(),
darkSurfaceContainerLow = darkColorScheme.surfaceContainerLow?.toString(),
darkSurfaceContainer = darkColorScheme.surfaceContainer?.toString(),
darkSurfaceContainerHigh = darkColorScheme.surfaceContainerHigh?.toString(),
darkSurfaceContainerHighest = darkColorScheme.surfaceContainerHighest?.toString(),
darkBackground = darkColorScheme.background?.toString(),
darkOnBackground = darkColorScheme.onBackground?.toString(),
darkSurfaceTint = darkColorScheme.surfaceTint?.toString(),
darkScrim = darkColorScheme.scrim?.toString(),
darkSurfaceVariant = darkColorScheme.surfaceVariant?.toString(),
)
}
}
fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
return when (color) {

View File

@ -2,16 +2,24 @@ package de.mm20.launcher2.themes
import android.content.Context
import android.util.Log
import de.mm20.launcher2.database.AppDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
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 kotlinx.coroutines.launch
import java.util.UUID
class ThemeRepository(
private val context: Context,
private val database: AppDatabase,
) {
private val scope = CoroutineScope(Dispatchers.IO + Job())
private val customTheme = MutableStateFlow(Theme(
id = UUID.randomUUID(),
builtIn = false,
@ -22,23 +30,26 @@ class ThemeRepository(
))
fun getThemes(): Flow<List<Theme>> {
return flowOf(getBuiltInThemes()).combine(customTheme) {
builtIn, custom ->
builtIn + custom
return database.themeDao().getAll().map {
getBuiltInThemes() + it.map { Theme(it) }
}
}
fun getTheme(id: UUID): Flow<Theme?> {
if (id == DefaultThemeId) return flowOf(getDefaultTheme())
return customTheme
return database.themeDao().get(id).map { it?.let { Theme(it) } }
}
fun createTheme(theme: Theme) {
scope.launch {
database.themeDao().insert(theme.toEntity())
}
}
fun updateTheme(theme: Theme) {
Log.d("MM20", "updateTheme: $theme")
customTheme.value = theme
scope.launch {
database.themeDao().update(theme.toEntity())
}
}
fun getThemeOrDefault(id: UUID): Flow<Theme> {