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 import org.koin.dsl.module
val themesModule = module { val themesModule = module {
factory { ThemeRepository(get()) } factory { ThemeRepository(get(), get()) }
} }

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.themes package de.mm20.launcher2.themes
import de.mm20.launcher2.database.entities.ThemeEntity
import hct.Hct import hct.Hct
import java.util.UUID 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 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( data class ColorRef(
val color: CorePaletteColor, val color: CorePaletteColor,
val tone: Int, val tone: Int,
) : Color { ) : Color {
override fun toString(): String { 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 FullCorePalette = CorePalette<Int>
typealias PartialCorePalette = CorePalette<Int?> typealias PartialCorePalette = CorePalette<Int?>
data class ColorScheme<out T: Color?>( data class ColorScheme<out T : Color?>(
val primary: T, val primary: T,
val onPrimary: T, val onPrimary: T,
val primaryContainer: T, val primaryContainer: T,
@ -105,7 +134,186 @@ data class Theme(
val corePalette: PartialCorePalette = EmptyCorePalette, val corePalette: PartialCorePalette = EmptyCorePalette,
val lightColorScheme: PartialColorScheme = DefaultLightColorScheme, val lightColorScheme: PartialColorScheme = DefaultLightColorScheme,
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme, 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 { fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
return when (color) { return when (color) {

View File

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