Add custom color schemes

This commit is contained in:
MM20 2022-04-14 23:01:19 +02:00
parent 2ca05d34f0
commit 08731887cb
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
14 changed files with 1374 additions and 5 deletions

View File

@ -387,6 +387,18 @@
<string name="preference_screen_colors">Color scheme</string>
<string name="preference_colors_default">Default</string>
<string name="preference_colors_bw">Black and White</string>
<string name="preference_colors_custom">Custom</string>
<string name="preference_custom_colors_a1">Primary</string>
<string name="preference_custom_colors_a2">Secondary</string>
<string name="preference_custom_colors_a3">Tertiary</string>
<string name="preference_custom_colors_n1">Neutral</string>
<string name="preference_custom_colors_n2">Neutral Variant</string>
<string name="preference_custom_colors_error">Error</string>
<string name="preference_custom_colors_advanced_mode">Advanced mode</string>
<string name="preference_custom_colors_simple_mode">Simple mode</string>
<string name="preference_colors_auto_generate">Generate from primary color</string>
<string name="preference_category_custom_colors_light">Light color scheme</string>
<string name="preference_category_custom_colors_dark">Dark color scheme</string>
<string name="preference_screen_about">About</string>
<string name="preference_version">Version</string>
<string name="preference_category_links">Links</string>

View File

@ -67,5 +67,6 @@ dependencies {
implementation(project(":i18n"))
implementation(project(":base"))
implementation(project(":crashreporter"))
implementation(project(":material-color-utilities"))
}

View File

@ -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
internal const val SchemaVersion = 6

View File

@ -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()
}
}
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()
}

View File

@ -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)
)
)
}
}

View File

@ -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;

View File

@ -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))
}
}
)
}
}

View File

@ -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()
}

View File

@ -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 = {

View File

@ -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)
}

View File

@ -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()
)
}
)
}
}
}
}
}

View File

@ -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()
}
}

View File

@ -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
)
)
}
}

View File

@ -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),
)
}