From 70f658f9f36ca43fa6b93dae474839f02da8f97a Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:08:04 +0200 Subject: [PATCH] Custom color schemes v2 - Part 3 Store custom themes in database --- .../java/de/mm20/launcher2/themes/Module.kt | 2 +- .../java/de/mm20/launcher2/themes/Theme.kt | 214 +++++++++++++++++- .../mm20/launcher2/themes/ThemeRepository.kt | 23 +- 3 files changed, 229 insertions(+), 10 deletions(-) diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt index cc11f784..4e9d0415 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Module.kt @@ -3,5 +3,5 @@ package de.mm20.launcher2.themes import org.koin.dsl.module val themesModule = module { - factory { ThemeRepository(get()) } + factory { ThemeRepository(get(), get()) } } \ No newline at end of file diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt index 5f31dfd9..d5377907 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt @@ -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(null, null, null, null, null, null) typealias FullCorePalette = CorePalette typealias PartialCorePalette = CorePalette -data class ColorScheme( +data class ColorScheme( 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 CorePalette.get(color: CorePaletteColor): T { return when (color) { diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt index a9418ea4..f1316f50 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt @@ -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> { - return flowOf(getBuiltInThemes()).combine(customTheme) { - builtIn, custom -> - builtIn + custom + return database.themeDao().getAll().map { + getBuiltInThemes() + it.map { Theme(it) } } } fun getTheme(id: UUID): Flow { 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 {