diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index 97b16574..c17c9056 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -387,6 +387,18 @@
Color scheme
Default
Black and White
+ Custom
+ Primary
+ Secondary
+ Tertiary
+ Neutral
+ Neutral Variant
+ Error
+ Advanced mode
+ Simple mode
+ Generate from primary color
+ Light color scheme
+ Dark color scheme
About
Version
Links
diff --git a/preferences/build.gradle.kts b/preferences/build.gradle.kts
index 957b4f8b..ae306e77 100644
--- a/preferences/build.gradle.kts
+++ b/preferences/build.gradle.kts
@@ -67,5 +67,6 @@ dependencies {
implementation(project(":i18n"))
implementation(project(":base"))
implementation(project(":crashreporter"))
+ implementation(project(":material-color-utilities"))
}
\ No newline at end of file
diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt
index 2a5d7bee..ec65bbb6 100644
--- a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt
+++ b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt
@@ -20,6 +20,7 @@ internal val Context.dataStore: LauncherDataStore by dataStore(
Migration_2_3(),
Migration_3_4(),
Migration_4_5(),
+ Migration_5_6(),
)
},
corruptionHandler = ReplaceFileCorruptionHandler {
@@ -29,4 +30,4 @@ internal val Context.dataStore: LauncherDataStore by dataStore(
}
)
-internal const val SchemaVersion = 5
\ No newline at end of file
+internal const val SchemaVersion = 6
\ No newline at end of file
diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt
index fc1f6eb8..05fbb045 100644
--- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt
+++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt
@@ -1,6 +1,7 @@
package de.mm20.launcher2.preferences
import android.content.Context
+import scheme.Scheme
fun createFactorySettings(context: Context): Settings {
return Settings.newBuilder()
@@ -10,6 +11,12 @@ fun createFactorySettings(context: Context): Settings {
.setTheme(Settings.AppearanceSettings.Theme.System)
.setColorScheme(Settings.AppearanceSettings.ColorScheme.Default)
.setDimWallpaper(false)
+ .setCustomColors(Settings.AppearanceSettings.CustomColors.newBuilder()
+ .setAdvancedMode(false)
+ .setBaseColors(DefaultCustomColorsBase)
+ .setLightScheme(DefaultLightCustomColorScheme)
+ .setDarkScheme(DefaultDarkCustomColorScheme)
+ )
.build()
)
.setWeather(
@@ -70,7 +77,7 @@ fun createFactorySettings(context: Context): Settings {
Settings.AppShortcutSearchSettings
.newBuilder()
.setEnabled(true)
- )
+ )
.setCalculatorSearch(
Settings.CalculatorSearchSettings
.newBuilder()
@@ -140,4 +147,83 @@ fun createFactorySettings(context: Context): Settings {
.setOpacity(1f)
)
.build()
-}
\ No newline at end of file
+}
+
+internal val DefaultCustomColorsBase: Settings.AppearanceSettings.CustomColors.BaseColors
+get() {
+ val scheme = Scheme.light(0xFFACE330.toInt())
+ return Settings.AppearanceSettings.CustomColors.BaseColors.newBuilder()
+ .setAccent1(scheme.primary)
+ .setAccent2(scheme.secondary)
+ .setAccent3(scheme.tertiary)
+ .setNeutral1(scheme.surface)
+ .setNeutral2(scheme.surfaceVariant)
+ .setError(scheme.error)
+ .build()
+}
+
+internal val DefaultLightCustomColorScheme: Settings.AppearanceSettings.CustomColors.Scheme
+ get() {
+ val scheme = Scheme.light(0xFFACE330.toInt())
+ return Settings.AppearanceSettings.CustomColors.Scheme.newBuilder()
+ .setPrimary(scheme.primary)
+ .setOnPrimary(scheme.onPrimary)
+ .setPrimaryContainer(scheme.primaryContainer)
+ .setOnPrimaryContainer(scheme.onPrimaryContainer)
+ .setSecondary(scheme.secondary)
+ .setOnSecondary(scheme.onSecondary)
+ .setSecondaryContainer(scheme.secondaryContainer)
+ .setOnSecondaryContainer(scheme.onSecondaryContainer)
+ .setTertiary(scheme.tertiary)
+ .setOnTertiary(scheme.onTertiary)
+ .setTertiaryContainer(scheme.tertiaryContainer)
+ .setOnTertiaryContainer(scheme.onTertiaryContainer)
+ .setBackground(scheme.background)
+ .setOnBackground(scheme.onBackground)
+ .setSurface(scheme.surface)
+ .setOnSurface(scheme.onSurface)
+ .setSurfaceVariant(scheme.surfaceVariant)
+ .setOnSurfaceVariant(scheme.onSurfaceVariant)
+ .setError(scheme.error)
+ .setOnError(scheme.onError)
+ .setErrorContainer(scheme.errorContainer)
+ .setOnErrorContainer(scheme.onErrorContainer)
+ .setInverseSurface(scheme.inverseSurface)
+ .setInverseOnSurface(scheme.inverseOnSurface)
+ .setInversePrimary(scheme.inversePrimary)
+ .setOutline(scheme.outline)
+ .build()
+ }
+
+internal val DefaultDarkCustomColorScheme: Settings.AppearanceSettings.CustomColors.Scheme
+ get() {
+ val scheme = Scheme.dark(0xFFACE330.toInt())
+ return Settings.AppearanceSettings.CustomColors.Scheme.newBuilder()
+ .setPrimary(scheme.primary)
+ .setOnPrimary(scheme.onPrimary)
+ .setPrimaryContainer(scheme.primaryContainer)
+ .setOnPrimaryContainer(scheme.onPrimaryContainer)
+ .setSecondary(scheme.secondary)
+ .setOnSecondary(scheme.onSecondary)
+ .setSecondaryContainer(scheme.secondaryContainer)
+ .setOnSecondaryContainer(scheme.onSecondaryContainer)
+ .setTertiary(scheme.tertiary)
+ .setOnTertiary(scheme.onTertiary)
+ .setTertiaryContainer(scheme.tertiaryContainer)
+ .setOnTertiaryContainer(scheme.onTertiaryContainer)
+ .setBackground(scheme.background)
+ .setOnBackground(scheme.onBackground)
+ .setSurface(scheme.surface)
+ .setOnSurface(scheme.onSurface)
+ .setSurfaceVariant(scheme.surfaceVariant)
+ .setOnSurfaceVariant(scheme.onSurfaceVariant)
+ .setError(scheme.error)
+ .setOnError(scheme.onError)
+ .setErrorContainer(scheme.errorContainer)
+ .setOnErrorContainer(scheme.onErrorContainer)
+ .setInverseSurface(scheme.inverseSurface)
+ .setInverseOnSurface(scheme.inverseOnSurface)
+ .setInversePrimary(scheme.inversePrimary)
+ .setOutline(scheme.outline)
+ .build()
+ }
\ No newline at end of file
diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_5_6.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_5_6.kt
new file mode 100644
index 00000000..5d73cd05
--- /dev/null
+++ b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_5_6.kt
@@ -0,0 +1,20 @@
+package de.mm20.launcher2.preferences.migrations
+
+import de.mm20.launcher2.preferences.DefaultCustomColorsBase
+import de.mm20.launcher2.preferences.DefaultDarkCustomColorScheme
+import de.mm20.launcher2.preferences.DefaultLightCustomColorScheme
+import de.mm20.launcher2.preferences.Settings
+
+class Migration_5_6: VersionedMigration(5, 6) {
+ override suspend fun applyMigrations(builder: Settings.Builder): Settings.Builder {
+ return builder.setAppearance(
+ builder.appearance.toBuilder()
+ .setCustomColors(Settings.AppearanceSettings.CustomColors.newBuilder()
+ .setAdvancedMode(false)
+ .setBaseColors(DefaultCustomColorsBase)
+ .setLightScheme(DefaultLightCustomColorScheme)
+ .setDarkScheme(DefaultDarkCustomColorScheme)
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto
index 0fde3f60..f7240e02 100644
--- a/preferences/src/main/proto/settings.proto
+++ b/preferences/src/main/proto/settings.proto
@@ -16,8 +16,52 @@ message Settings {
Default = 0;
BlackAndWhite = 1;
DebugMaterialYouCompat = 2;
+ Custom = 3;
}
ColorScheme color_scheme = 6;
+ message CustomColors {
+ bool advanced_mode = 1;
+ message BaseColors {
+ uint32 accent1 = 1;
+ uint32 accent2 = 2;
+ uint32 accent3 = 3;
+ uint32 neutral1 = 4;
+ uint32 neutral2 = 5;
+ uint32 error = 6;
+ }
+ BaseColors base_colors = 2;
+ message Scheme {
+ uint32 primary = 1;
+ uint32 on_primary = 2;
+ uint32 primary_container = 3;
+ uint32 on_primary_container = 4;
+ uint32 secondary = 5;
+ uint32 on_secondary = 6;
+ uint32 secondary_container = 7;
+ uint32 on_secondary_container = 8;
+ uint32 tertiary = 9;
+ uint32 on_tertiary = 10;
+ uint32 tertiary_container = 11;
+ uint32 on_tertiary_container = 12;
+ uint32 background = 13;
+ uint32 on_background = 14;
+ uint32 surface = 15;
+ uint32 on_surface = 16;
+ uint32 surface_variant = 17;
+ uint32 on_surface_variant = 18;
+ uint32 outline = 19;
+ uint32 inverse_surface = 20;
+ uint32 inverse_on_surface = 21;
+ uint32 inverse_primary = 22;
+ uint32 error = 23;
+ uint32 on_error = 24;
+ uint32 error_container = 25;
+ uint32 on_error_container = 26;
+ }
+ Scheme light_scheme = 3;
+ Scheme dark_scheme = 4;
+ }
+ CustomColors custom_colors = 8;
bool dim_wallpaper = 7;
}
AppearanceSettings appearance = 2;
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ColorPreference.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ColorPreference.kt
new file mode 100644
index 00000000..d20db0de
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/ColorPreference.kt
@@ -0,0 +1,95 @@
+package de.mm20.launcher2.ui.component.preferences
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.godaddy.android.colorpicker.ClassicColorPicker
+import de.mm20.launcher2.ui.ktx.toHexString
+
+@Composable
+fun ColorPreference(
+ title: String,
+ summary: String? = null,
+ value: Color?,
+ onValueChanged: (Color?) -> Unit = {}
+) {
+ var showDialog by remember { mutableStateOf(false) }
+ Preference(
+ title = title,
+ summary = summary,
+ controls = {
+ value?.let {
+ Surface(
+ color = it,
+ shape = CircleShape,
+ modifier = Modifier
+ .padding(vertical = 12.dp)
+ .size(36.dp)
+ ) {}
+ }
+ },
+ onClick = {
+ showDialog = true
+ }
+ )
+ if (showDialog) {
+ var color by remember(value) { mutableStateOf(value) }
+ AlertDialog(
+ onDismissRequest = { showDialog = false },
+ title = {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleLarge
+ )
+ },
+ text = {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+
+ ClassicColorPicker(
+ color = value ?: Color.Black,
+ onColorChanged = {
+ color = it.toColor()
+ },
+ showAlphaBar = false,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ )
+
+ var hexValue by remember(color) {
+ mutableStateOf(
+ color?.toHexString() ?: "#000000"
+ )
+ }
+
+ TextField(
+ modifier = Modifier.padding(top = 16.dp),
+ value = hexValue,
+ onValueChange = {
+ hexValue = it
+ if (Regex("#[0-9a-fA-F]{6}").matches(it)) {
+ val hex = it.substring(1).toIntOrNull(16) ?: return@TextField
+ color = Color(hex).copy(alpha = 1f)
+ }
+ }
+ )
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ onValueChanged(color)
+ showDialog = false
+ }) {
+ Text(stringResource(android.R.string.ok))
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
index 08866267..2c824331 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
@@ -28,6 +28,7 @@ import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemeSettingsScreen
+import de.mm20.launcher2.ui.settings.colorscheme.CustomColorSchemeSettingsScreen
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
@@ -88,6 +89,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/appearance/colorscheme") {
ColorSchemeSettingsScreen()
}
+ composable("settings/appearance/colorscheme/custom") {
+ CustomColorSchemeSettingsScreen()
+ }
composable("settings/appearance/cards") {
CardsSettingsScreen()
}
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
index 7a6b160a..89c03d70 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
@@ -71,6 +71,7 @@ fun AppearanceSettingsScreen() {
summary = when (colorScheme) {
ColorScheme.Default -> stringResource(R.string.preference_colors_default)
ColorScheme.BlackAndWhite -> stringResource(R.string.preference_colors_bw)
+ ColorScheme.Custom -> stringResource(R.string.preference_colors_custom)
else -> null
},
onClick = {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
index 7a01c823..57afdbde 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
@@ -22,11 +22,13 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
+import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.theme.colorSchemeAsState
@Composable
fun ColorSchemeSettingsScreen() {
val viewModel: ColorSchemeSettingsScreenVM = viewModel()
+ val navController = LocalNavController.current
PreferenceScreen(title = stringResource(R.string.preference_screen_colors)) {
item {
@@ -36,6 +38,7 @@ fun ColorSchemeSettingsScreen() {
val items = mutableListOf(
AppearanceSettings.ColorScheme.Default to stringResource(R.string.preference_colors_default),
AppearanceSettings.ColorScheme.BlackAndWhite to stringResource(R.string.preference_colors_bw),
+ AppearanceSettings.ColorScheme.Custom to stringResource(R.string.preference_colors_custom),
)
if (BuildConfig.DEBUG && isAtLeastApiLevel(27)) {
@@ -47,7 +50,12 @@ fun ColorSchemeSettingsScreen() {
Preference(
title = cs.second,
icon = if (colorScheme == cs.first) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
- onClick = { viewModel.setColorScheme(cs.first) },
+ onClick = {
+ viewModel.setColorScheme(cs.first)
+ if (cs.first == AppearanceSettings.ColorScheme.Custom) {
+ navController?.navigate("settings/appearance/colorscheme/custom")
+ }
+ },
controls = {
ColorSchemePreview(scheme)
}
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreen.kt
new file mode 100644
index 00000000..2e6e26dd
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreen.kt
@@ -0,0 +1,852 @@
+package de.mm20.launcher2.ui.settings.colorscheme
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.MoreVert
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.viewmodel.compose.viewModel
+import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.component.preferences.ColorPreference
+import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
+import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
+
+@Composable
+fun CustomColorSchemeSettingsScreen() {
+ val viewModel: CustomColorSchemeSettingsScreenVM = viewModel()
+
+ val advancedMode by viewModel.advancedMode.observeAsState()
+
+ PreferenceScreen(
+ title = stringResource(R.string.preference_screen_colors),
+ topBarActions = {
+ var showOverflowMenu by remember { mutableStateOf(false) }
+ IconButton(onClick = { showOverflowMenu = true }) {
+ Icon(imageVector = Icons.Rounded.MoreVert, contentDescription = null)
+ }
+ DropdownMenu(
+ expanded = showOverflowMenu,
+ onDismissRequest = { showOverflowMenu = false }) {
+ if (advancedMode == false) {
+ DropdownMenuItem(
+ text = { Text("Generate from primary color") },
+ onClick = {
+ viewModel.generateFromPrimaryColor()
+ showOverflowMenu = false
+ }
+ )
+ }
+ DropdownMenuItem(
+ text = {
+ Text(
+ stringResource(
+ if (advancedMode == true) {
+ R.string.preference_custom_colors_simple_mode
+ } else {
+ R.string.preference_custom_colors_advanced_mode
+ }
+ )
+
+ )
+ },
+ onClick = {
+ viewModel.setAdvancedMode(advancedMode?.not() ?: true)
+ showOverflowMenu = false
+ }
+ )
+ }
+ }
+ ) {
+ if (advancedMode == false) {
+ item {
+ PreferenceCategory {
+ val baseColors by viewModel.baseColors.observeAsState()
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_a1),
+ value = baseColors?.let { Color(it.accent1) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setAccent1(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_a2),
+ value = baseColors?.let { Color(it.accent2) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setAccent2(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_a3),
+ value = baseColors?.let { Color(it.accent3) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setAccent3(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_n1),
+ value = baseColors?.let { Color(it.neutral1) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setNeutral1(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_n2),
+ value = baseColors?.let { Color(it.neutral2) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setNeutral2(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = stringResource(R.string.preference_custom_colors_error),
+ value = baseColors?.let { Color(it.error) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = baseColors ?: return@ColorPreference
+ viewModel.setBaseColors(
+ colors.toBuilder()
+ .setError(it.toArgb())
+ .build()
+ )
+ }
+ )
+ }
+ }
+ }
+ if (advancedMode == true) {
+ item {
+ PreferenceCategory(stringResource(R.string.preference_category_custom_colors_light)) {
+ val lightScheme by viewModel.lightScheme.observeAsState()
+ ColorPreference(
+ title = "Primary",
+ value = lightScheme?.let { Color(it.primary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setPrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Primary",
+ value = lightScheme?.let { Color(it.onPrimary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnPrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Primary Container",
+ value = lightScheme?.let { Color(it.primaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setPrimaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Primary Container",
+ value = lightScheme?.let { Color(it.onPrimaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnPrimaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Secondary",
+ value = lightScheme?.let { Color(it.secondary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setSecondary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Secondary",
+ value = lightScheme?.let { Color(it.onSecondary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnSecondary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Secondary Container",
+ value = lightScheme?.let { Color(it.secondaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setSecondaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Secondary Container",
+ value = lightScheme?.let { Color(it.onSecondaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnSecondaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Tertiary",
+ value = lightScheme?.let { Color(it.tertiary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setTertiary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Tertiary",
+ value = lightScheme?.let { Color(it.onTertiary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnTertiary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Tertiary Container",
+ value = lightScheme?.let { Color(it.tertiaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setTertiaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Tertiary Container",
+ value = lightScheme?.let { Color(it.onTertiaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnTertiaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Background",
+ value = lightScheme?.let { Color(it.background) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setBackground(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Background",
+ value = lightScheme?.let { Color(it.onBackground) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnBackground(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Surface",
+ value = lightScheme?.let { Color(it.surface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Surface",
+ value = lightScheme?.let { Color(it.onSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Surface Variant",
+ value = lightScheme?.let { Color(it.surfaceVariant) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setSurfaceVariant(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Surface Variant",
+ value = lightScheme?.let { Color(it.onSurfaceVariant) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnSurfaceVariant(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Error",
+ value = lightScheme?.let { Color(it.error) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setError(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Error",
+ value = lightScheme?.let { Color(it.onError) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnError(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Error Container",
+ value = lightScheme?.let { Color(it.errorContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setErrorContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Error Container",
+ value = lightScheme?.let { Color(it.onErrorContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOnErrorContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Inverse Primary",
+ value = lightScheme?.let { Color(it.inversePrimary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setInversePrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Inverse Surface",
+ value = lightScheme?.let { Color(it.inverseSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setInverseSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Inverse On Surface",
+ value = lightScheme?.let { Color(it.inverseOnSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setInverseOnSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Outline",
+ value = lightScheme?.let { Color(it.outline) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = lightScheme ?: return@ColorPreference
+ viewModel.setLightScheme(
+ colors.toBuilder()
+ .setOutline(it.toArgb())
+ .build()
+ )
+ }
+ )
+ }
+
+ PreferenceCategory(stringResource(R.string.preference_category_custom_colors_light)) {
+ val darkScheme by viewModel.darkScheme.observeAsState()
+ ColorPreference(
+ title = "Primary",
+ value = darkScheme?.let { Color(it.primary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setPrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Primary",
+ value = darkScheme?.let { Color(it.onPrimary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnPrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Primary Container",
+ value = darkScheme?.let { Color(it.primaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setPrimaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Primary Container",
+ value = darkScheme?.let { Color(it.onPrimaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnPrimaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Secondary",
+ value = darkScheme?.let { Color(it.secondary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setSecondary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Secondary",
+ value = darkScheme?.let { Color(it.onSecondary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnSecondary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Secondary Container",
+ value = darkScheme?.let { Color(it.secondaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setSecondaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Secondary Container",
+ value = darkScheme?.let { Color(it.onSecondaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnSecondaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Tertiary",
+ value = darkScheme?.let { Color(it.tertiary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setTertiary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Tertiary",
+ value = darkScheme?.let { Color(it.onTertiary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnTertiary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Tertiary Container",
+ value = darkScheme?.let { Color(it.tertiaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setTertiaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Tertiary Container",
+ value = darkScheme?.let { Color(it.onTertiaryContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnTertiaryContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Background",
+ value = darkScheme?.let { Color(it.background) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setBackground(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Background",
+ value = darkScheme?.let { Color(it.onBackground) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnBackground(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Surface",
+ value = darkScheme?.let { Color(it.surface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Surface",
+ value = darkScheme?.let { Color(it.onSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Surface Variant",
+ value = darkScheme?.let { Color(it.surfaceVariant) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setSurfaceVariant(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Surface Variant",
+ value = darkScheme?.let { Color(it.onSurfaceVariant) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnSurfaceVariant(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Error",
+ value = darkScheme?.let { Color(it.error) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setError(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Error",
+ value = darkScheme?.let { Color(it.onError) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnError(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Error Container",
+ value = darkScheme?.let { Color(it.errorContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setErrorContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "On Error Container",
+ value = darkScheme?.let { Color(it.onErrorContainer) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOnErrorContainer(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Inverse Primary",
+ value = darkScheme?.let { Color(it.inversePrimary) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setInversePrimary(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Inverse Surface",
+ value = darkScheme?.let { Color(it.inverseSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setInverseSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+ ColorPreference(
+ title = "Inverse On Surface",
+ value = darkScheme?.let { Color(it.inverseOnSurface) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setInverseOnSurface(it.toArgb())
+ .build()
+ )
+ }
+ )
+
+ ColorPreference(
+ title = "Outline",
+ value = darkScheme?.let { Color(it.outline) },
+ onValueChanged = {
+ if (it == null) return@ColorPreference
+ val colors = darkScheme ?: return@ColorPreference
+ viewModel.setDarkScheme(
+ colors.toBuilder()
+ .setOutline(it.toArgb())
+ .build()
+ )
+ }
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreenVM.kt
new file mode 100644
index 00000000..8a984669
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CustomColorSchemeSettingsScreenVM.kt
@@ -0,0 +1,194 @@
+package de.mm20.launcher2.ui.settings.colorscheme
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import de.mm20.launcher2.preferences.LauncherDataStore
+import de.mm20.launcher2.preferences.Settings
+import de.mm20.launcher2.preferences.Settings.AppearanceSettings.CustomColors
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import palettes.TonalPalette
+import scheme.Scheme
+
+class CustomColorSchemeSettingsScreenVM : ViewModel(), KoinComponent {
+ private val dataStore: LauncherDataStore by inject()
+
+ val advancedMode = dataStore.data.map { it.appearance.customColors.advancedMode }.asLiveData()
+ fun setAdvancedMode(advancedMode: Boolean) {
+ viewModelScope.launch {
+ val lightScheme = dataStore.updateData {
+ val customColors = it.appearance.customColors.toBuilder()
+ .setAdvancedMode(advancedMode)
+ it.toBuilder()
+ .setAppearance(
+ it.appearance.toBuilder()
+ .setCustomColors(customColors)
+ )
+ .build()
+ }.appearance.customColors.lightScheme
+
+ if (!advancedMode) {
+ setBaseColors(CustomColors.BaseColors
+ .newBuilder()
+ .setAccent1(lightScheme.primary)
+ .setAccent2(lightScheme.secondary)
+ .setAccent3(lightScheme.tertiary)
+ .setNeutral1(lightScheme.surface)
+ .setNeutral2(lightScheme.surface)
+ .setError(lightScheme.error)
+ .build()
+ )
+ }
+ }
+ }
+
+ fun generateFromPrimaryColor() {
+ viewModelScope.launch {
+ val primary = dataStore.data.map { it.appearance.customColors.baseColors.accent1 }.first()
+ val scheme = Scheme.light(primary)
+ setBaseColors(
+ CustomColors.BaseColors.newBuilder()
+ .setAccent1(scheme.primary)
+ .setAccent2(scheme.secondary)
+ .setAccent3(scheme.tertiary)
+ .setNeutral1(scheme.surface)
+ .setNeutral2(scheme.surfaceVariant)
+ .setError(scheme.error)
+ .build()
+ )
+ }
+ }
+
+ val baseColors = dataStore.data.map { it.appearance.customColors.baseColors }.asLiveData()
+ fun setBaseColors(baseColors: CustomColors.BaseColors) {
+ viewModelScope.launch {
+ dataStore.updateData {
+ it.toBuilder()
+ .setAppearance(
+ it.appearance.toBuilder()
+ .setCustomColors(
+ it.appearance.customColors.toBuilder()
+ .setBaseColors(baseColors)
+ )
+ )
+ .build()
+ }
+ setDarkScheme(baseColorsToDarkScheme(baseColors))
+ setLightScheme(baseColorsToLightScheme(baseColors))
+ }
+ }
+
+ val darkScheme = dataStore.data.map { it.appearance.customColors.darkScheme }.asLiveData()
+ fun setDarkScheme(darkScheme: CustomColors.Scheme) {
+ viewModelScope.launch {
+ dataStore.updateData {
+ it.toBuilder()
+ .setAppearance(
+ it.appearance.toBuilder()
+ .setCustomColors(
+ it.appearance.customColors.toBuilder()
+ .setDarkScheme(darkScheme)
+ )
+ )
+ .build()
+ }
+ }
+ }
+
+ val lightScheme = dataStore.data.map { it.appearance.customColors.lightScheme }.asLiveData()
+ fun setLightScheme(lightScheme: CustomColors.Scheme) {
+ viewModelScope.launch {
+ dataStore.updateData {
+ it.toBuilder()
+ .setAppearance(
+ it.appearance.toBuilder()
+ .setCustomColors(
+ it.appearance.customColors.toBuilder()
+ .setLightScheme(lightScheme)
+ )
+ )
+ .build()
+ }
+ }
+ }
+
+ private fun baseColorsToDarkScheme(baseColors: CustomColors.BaseColors): CustomColors.Scheme {
+ val a1 = TonalPalette.fromInt(baseColors.accent1)
+ val a2 = TonalPalette.fromInt(baseColors.accent2)
+ val a3 = TonalPalette.fromInt(baseColors.accent3)
+ val n1 = TonalPalette.fromInt(baseColors.neutral1)
+ val n2 = TonalPalette.fromInt(baseColors.neutral2)
+ val error = TonalPalette.fromInt(baseColors.error)
+
+ return CustomColors.Scheme.newBuilder()
+ .setPrimary(a1.tone(80))
+ .setOnPrimary(a1.tone(20))
+ .setPrimaryContainer(a1.tone(30))
+ .setOnPrimaryContainer(a1.tone(90))
+ .setSecondary(a2.tone(80))
+ .setOnSecondary(a2.tone(20))
+ .setSecondaryContainer(a2.tone(30))
+ .setOnSecondaryContainer(a2.tone(90))
+ .setTertiary(a3.tone(80))
+ .setOnTertiary(a3.tone(20))
+ .setTertiaryContainer(a3.tone(30))
+ .setOnTertiaryContainer(a3.tone(90))
+ .setError(error.tone(80))
+ .setOnError(error.tone(20))
+ .setErrorContainer(error.tone(30))
+ .setOnErrorContainer(error.tone(80))
+ .setBackground(n1.tone(10))
+ .setOnBackground(n1.tone(90))
+ .setSurface(n1.tone(10))
+ .setOnSurface(n1.tone(90))
+ .setSurfaceVariant(n2.tone(30))
+ .setOnSurfaceVariant(n2.tone(80))
+ .setOutline(n2.tone(60))
+ .setInverseSurface(n1.tone(90))
+ .setInverseOnSurface(n1.tone(20))
+ .setInversePrimary(a1.tone(40))
+ .build()
+ }
+
+ private fun baseColorsToLightScheme(baseColors: CustomColors.BaseColors): CustomColors.Scheme {
+ val a1 = TonalPalette.fromInt(baseColors.accent1)
+ val a2 = TonalPalette.fromInt(baseColors.accent2)
+ val a3 = TonalPalette.fromInt(baseColors.accent3)
+ val n1 = TonalPalette.fromInt(baseColors.neutral1)
+ val n2 = TonalPalette.fromInt(baseColors.neutral2)
+ val error = TonalPalette.fromInt(baseColors.error)
+
+ return CustomColors.Scheme.newBuilder()
+ .setPrimary(a1.tone(40))
+ .setOnPrimary(a1.tone(100))
+ .setPrimaryContainer(a1.tone(90))
+ .setOnPrimaryContainer(a1.tone(10))
+ .setSecondary(a2.tone(40))
+ .setOnSecondary(a2.tone(100))
+ .setSecondaryContainer(a2.tone(90))
+ .setOnSecondaryContainer(a2.tone(10))
+ .setTertiary(a3.tone(40))
+ .setOnTertiary(a3.tone(100))
+ .setTertiaryContainer(a3.tone(90))
+ .setOnTertiaryContainer(a3.tone(10))
+ .setError(error.tone(40))
+ .setOnError(error.tone(100))
+ .setErrorContainer(error.tone(90))
+ .setOnErrorContainer(error.tone(10))
+ .setBackground(n1.tone(99))
+ .setOnBackground(n1.tone(10))
+ .setSurface(n1.tone(99))
+ .setOnSurface(n1.tone(10))
+ .setSurfaceVariant(n2.tone(90))
+ .setOnSurfaceVariant(n2.tone(30))
+ .setOutline(n2.tone(50))
+ .setInverseSurface(n1.tone(20))
+ .setInverseOnSurface(n1.tone(95))
+ .setInversePrimary(a1.tone(80))
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt b/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
index 43e79d02..a2d004dc 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
@@ -57,6 +57,18 @@ fun colorSchemeAsState(colorScheme: AppearanceSettings.ColorScheme): MutableStat
)
}
}
+ AppearanceSettings.ColorScheme.Custom -> {
+ val colors by remember(darkTheme) {
+ dataStore.data.map { if (darkTheme) it.appearance.customColors.darkScheme else it.appearance.customColors.lightScheme }
+ }.collectAsState(null)
+ val state = remember(colors, darkTheme) {
+ mutableStateOf(
+ colors?.let { CustomColorScheme(it) }
+ ?: if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme
+ )
+ }
+ return state
+ }
else -> {
if (Build.VERSION.SDK_INT >= 27 && (Build.VERSION.SDK_INT < 31 || colorScheme == AppearanceSettings.ColorScheme.DebugMaterialYouCompat)) {
val wallpaperColors by wallpaperColorsAsState()
@@ -71,7 +83,9 @@ fun colorSchemeAsState(colorScheme: AppearanceSettings.ColorScheme): MutableStat
if (Build.VERSION.SDK_INT >= 31) {
return remember(darkTheme) {
mutableStateOf(
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(
+ context
+ )
)
}
}
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt b/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt
new file mode 100644
index 00000000..c55f4e90
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt
@@ -0,0 +1,37 @@
+package de.mm20.launcher2.ui.theme.colorscheme
+
+import androidx.compose.material3.ColorScheme
+import androidx.compose.ui.graphics.Color
+import de.mm20.launcher2.preferences.Settings
+
+fun CustomColorScheme(colors: Settings.AppearanceSettings.CustomColors.Scheme) : ColorScheme {
+ return ColorScheme(
+ primary = Color(colors.primary),
+ onPrimary = Color(colors.onPrimary),
+ primaryContainer = Color(colors.primaryContainer),
+ onPrimaryContainer = Color(colors.onPrimaryContainer),
+ secondary = Color(colors.secondary),
+ onSecondary = Color(colors.onSecondary),
+ secondaryContainer = Color(colors.secondaryContainer),
+ onSecondaryContainer = Color(colors.onSecondaryContainer),
+ tertiary = Color(colors.tertiary),
+ onTertiary = Color(colors.onTertiary),
+ tertiaryContainer = Color(colors.tertiaryContainer),
+ onTertiaryContainer = Color(colors.onTertiaryContainer),
+ background = Color(colors.background),
+ onBackground = Color(colors.onBackground),
+ surface = Color(colors.surface),
+ onSurface = Color(colors.onSurface),
+ surfaceVariant = Color(colors.surfaceVariant),
+ onSurfaceVariant = Color(colors.onSurfaceVariant),
+ outline = Color(colors.outline),
+ inverseSurface = Color(colors.inverseSurface),
+ inverseOnSurface = Color(colors.inverseOnSurface),
+ inversePrimary = Color(colors.inversePrimary),
+ surfaceTint = Color(colors.primary),
+ error = Color(colors.error),
+ onError = Color(colors.onError),
+ errorContainer = Color(colors.errorContainer),
+ onErrorContainer = Color(colors.onErrorContainer),
+ )
+}
\ No newline at end of file