Split custom color scheme preferences to multiple files

This commit is contained in:
MM20 2023-08-22 16:50:20 +02:00
parent 68df2cc89e
commit cf6cbf20f4
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
5 changed files with 730 additions and 597 deletions

View File

@ -0,0 +1,66 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import hct.Hct
@Composable
fun ColorSwatch(
color: Color,
modifier: Modifier = Modifier,
selected: Boolean = false
) {
val darkTheme = LocalDarkTheme.current
val iconColor = Color(Hct.fromInt(color.toArgb()).let {
val tone = if (darkTheme) {
if (it.tone.toInt() > 40) 30f
else 60f
} else {
if (it.tone.toInt() < 60) 80f
else 40f
}
it.apply {
this.tone = tone.toDouble()
}.toInt()
})
val borderColor = Color(Hct.fromInt(color.toArgb()).let {
val tone = if (darkTheme) 30f else 80f
it.apply {
this.tone = tone.toDouble()
}.toInt()
})
Box(
modifier = modifier
.clip(CircleShape)
.border(
if (selected) 4.dp else 1.dp,
borderColor,
CircleShape
)
.background(color),
contentAlignment = Alignment.Center
) {
if (selected) {
Icon(
Icons.Rounded.CheckCircle,
null,
modifier = Modifier.size(32.dp),
tint = iconColor,
)
}
}
}

View File

@ -0,0 +1,127 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AutoFixHigh
import androidx.compose.material.icons.rounded.SettingsSuggest
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltipBox
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
fun CorePaletteColorPreference(
title: String,
value: Int?,
onValueChange: (Int?) -> Unit,
defaultValue: Int,
modifier: Modifier = Modifier,
autoGenerate: (() -> Int?)? = null,
) {
var showDialog by remember { mutableStateOf(false) }
PlainTooltipBox(tooltip = { Text(title) }) {
ColorSwatch(
color = Color(value ?: defaultValue),
modifier = modifier
.size(48.dp)
.tooltipTrigger()
.clickable { showDialog = true },
)
}
if (showDialog) {
BottomSheetDialog(onDismissRequest = { showDialog = false }) {
Column(
modifier = Modifier.padding(it)
) {
SwitchPreference(
icon = Icons.Rounded.SettingsSuggest,
title = "Use system default",
value = value == null,
onValueChanged = {
onValueChange(if (it) null else defaultValue)
}
)
AnimatedVisibility(
value != null,
enter = expandVertically(
expandFrom = Alignment.Top,
),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
)
) {
Column {
HorizontalDivider(
modifier = Modifier.padding(bottom = 24.dp)
)
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(value ?: defaultValue),
onColorChanged = {
onValueChange(it.toArgb())
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
state = colorPickerState,
)
if (autoGenerate != null) {
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
val autoGenerated = autoGenerate()
onValueChange(autoGenerated)
if (autoGenerated != null) {
colorPickerState.setColor(Color(autoGenerated))
}
}
) {
Icon(
Icons.Rounded.AutoFixHigh, null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text("From primary color")
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,427 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Colorize
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltipBox
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.Color as ThemeColor
import de.mm20.launcher2.themes.ColorRef
import de.mm20.launcher2.themes.CorePaletteColor
import de.mm20.launcher2.themes.FullCorePalette
import de.mm20.launcher2.themes.StaticColor
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.ktx.hct
import hct.Hct
import kotlin.math.roundToInt
@Composable
fun ThemeColorPreference(
title: String,
value: de.mm20.launcher2.themes.Color?,
corePalette: FullCorePalette,
onValueChange: (ThemeColor?) -> Unit,
defaultValue: ThemeColor,
modifier: Modifier = Modifier,
) {
var showDialog by remember { mutableStateOf(false) }
val actualValue = value ?: defaultValue
PlainTooltipBox(tooltip = { Text(title) }) {
ColorSwatch(
color = Color(actualValue.get(corePalette)),
modifier = modifier
.size(48.dp)
.tooltipTrigger()
.clickable { showDialog = true },
)
}
if (showDialog) {
BottomSheetDialog(onDismissRequest = { showDialog = false }) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(it),
) {
SingleChoiceSegmentedButtonRow(
modifier = Modifier.fillMaxWidth()
) {
SegmentedButton(
selected = actualValue is ColorRef,
onClick = {
if (actualValue is ColorRef) return@SegmentedButton
onValueChange(defaultValue)
},
icon = {
SegmentedButtonDefaults.SegmentedButtonIcon(
active = actualValue is ColorRef,
) {
Icon(
Icons.Rounded.Palette,
null,
modifier = Modifier
.size(SegmentedButtonDefaults.IconSize)
)
}
},
shape = SegmentedButtonDefaults.shape(position = 0, count = 2)
) {
Text("From palette")
}
SegmentedButton(
selected = actualValue is StaticColor,
onClick = {
onValueChange(StaticColor(actualValue.get(corePalette)))
},
icon = {
SegmentedButtonDefaults.SegmentedButtonIcon(
active = actualValue is StaticColor,
) {
Icon(
Icons.Rounded.Colorize,
null,
modifier = Modifier
.size(SegmentedButtonDefaults.IconSize)
)
}
},
shape = SegmentedButtonDefaults.shape(position = 1, count = 2)
) {
Text("Custom")
}
}
AnimatedContent(
actualValue,
label = "AnimatedContent",
contentKey = { it is StaticColor }
) { themeColor ->
Column {
if (themeColor is StaticColor) {
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(themeColor.color),
onColorChanged = {
onValueChange(StaticColor(it.toArgb()))
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
.align(Alignment.CenterHorizontally),
state = colorPickerState
)
} else if (themeColor is ColorRef) {
val hct = Hct.fromInt(corePalette.get(themeColor.color))
val hue = hct.hue.toFloat()
val chroma = hct.chroma.toFloat()
var tone by remember(value == null) { mutableStateOf(themeColor.tone.toFloat()) }
Row(
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Primary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Primary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Primary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Secondary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Secondary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Secondary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Tertiary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Tertiary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Tertiary,
)
}
Row(
modifier = Modifier.padding(bottom = 16.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Neutral)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Neutral,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Neutral,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.NeutralVariant)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.NeutralVariant,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.NeutralVariant,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Error)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Error,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Error,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Text(
text = "C",
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center
)
Slider(
modifier = Modifier.weight(1f),
value = tone,
valueRange = 0f..100f,
onValueChange = {
tone = it
onValueChange(themeColor.copy(tone = it.roundToInt()))
},
track = {
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
) {
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
0f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
10f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
20f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
30f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
40f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
50f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
60f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
70f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
80f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
90f
),
androidx.compose.ui.graphics.Color.hct(
hue,
chroma,
100f
),
)
),
style = Fill,
cornerRadius = CornerRadius(
10.dp.toPx(),
10.dp.toPx()
)
)
}
},
thumb = {
Box(
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 8.dp)
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(androidx.compose.ui.graphics.Color.White)
)
}
)
Text(
text = tone.roundToInt().toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = { onValueChange(null) }
) {
Icon(
Icons.Rounded.RestartAlt, null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text("Restore default")
}
}
}
}
}
}
}

View File

@ -0,0 +1,105 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.DarkMode
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun ThemePreferenceCategory(
title: String,
previewColorScheme: ColorScheme,
darkMode: Boolean,
onDarkModeChanged: (Boolean) -> Unit,
colorPreferences: @Composable () -> Unit = {},
preview: @Composable FlowRowScope.() -> Unit
) {
Column(
modifier = Modifier.padding(top = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f)
)
SingleChoiceSegmentedButtonRow {
SegmentedButton(
shape = SegmentedButtonDefaults.shape(position = 0, count = 2),
selected = !darkMode,
onClick = { onDarkModeChanged(false) }
) {
Icon(Icons.Rounded.LightMode, null)
}
SegmentedButton(
shape = SegmentedButtonDefaults.shape(position = 1, count = 2),
selected = darkMode,
onClick = { onDarkModeChanged(true) }
) {
Icon(Icons.Rounded.DarkMode, null)
}
}
}
MaterialTheme(
colorScheme = previewColorScheme
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
)
) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
) {
preview()
}
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(16.dp)
) {
colorPreferences()
}
HorizontalDivider()
}
}

View File

@ -1,47 +1,22 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AutoFixHigh
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Colorize
import androidx.compose.material.icons.rounded.DarkMode
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.SettingsSuggest
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.FilterChip
@ -53,11 +28,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltipBox
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
import androidx.compose.material3.Snackbar
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
@ -71,16 +41,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -88,32 +50,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.themes.ColorRef
import de.mm20.launcher2.themes.CorePaletteColor
import de.mm20.launcher2.themes.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme
import de.mm20.launcher2.themes.FullCorePalette
import de.mm20.launcher2.themes.StaticColor
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.themes.merge
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.ktx.hct
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
import de.mm20.launcher2.ui.theme.colorscheme.systemCorePalette
import hct.Hct
import palettes.CorePalette
import java.util.UUID
import kotlin.math.roundToInt
import de.mm20.launcher2.themes.Color as ThemeColor
@Composable
fun ThemeSettingsScreen(themeId: UUID) {
@ -986,26 +934,26 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
ThemeColorPreference(
title = "On Surface Variant",
value = selectedColorScheme.surfaceVariant,
value = selectedColorScheme.onSurfaceVariant,
corePalette = mergedCorePalette,
onValueChange = {
viewModel.updateTheme(
if (previewDarkTheme) {
theme!!.copy(
darkColorScheme = theme!!.darkColorScheme.copy(
surfaceVariant = it
onSurfaceVariant = it
)
)
} else {
theme!!.copy(
lightColorScheme = theme!!.lightColorScheme.copy(
surfaceVariant = it
onSurfaceVariant = it
)
)
}
)
},
defaultValue = selectedDefaultScheme.surfaceVariant,
defaultValue = selectedDefaultScheme.onSurfaceVariant,
modifier = Modifier.padding(end = 12.dp),
)
},
@ -1098,7 +1046,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
.align(Alignment.CenterVertically)
) {}
VerticalDivider(
modifier = Modifier.padding(end = 16.dp),
modifier = Modifier.height(64.dp).padding(end = 16.dp),
)
}
}
@ -1314,543 +1262,3 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
@Composable
fun CorePaletteColorPreference(
title: String,
value: Int?,
onValueChange: (Int?) -> Unit,
defaultValue: Int,
modifier: Modifier = Modifier,
autoGenerate: (() -> Int?)? = null,
) {
var showDialog by remember { mutableStateOf(false) }
PlainTooltipBox(tooltip = { Text(title) }) {
ColorSwatch(
color = Color(value ?: defaultValue),
modifier = modifier
.size(48.dp)
.tooltipTrigger()
.clickable { showDialog = true },
)
}
if (showDialog) {
BottomSheetDialog(onDismissRequest = { showDialog = false }) {
Column(
modifier = Modifier.padding(it)
) {
SwitchPreference(
icon = Icons.Rounded.SettingsSuggest,
title = "Use system default",
value = value == null,
onValueChanged = {
onValueChange(if (it) null else defaultValue)
}
)
AnimatedVisibility(
value != null,
enter = expandVertically(
expandFrom = Alignment.Top,
),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
)
) {
Column {
HorizontalDivider(
modifier = Modifier.padding(bottom = 24.dp)
)
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(value ?: defaultValue),
onColorChanged = {
onValueChange(it.toArgb())
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
state = colorPickerState,
)
if (autoGenerate != null) {
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
val autoGenerated = autoGenerate()
onValueChange(autoGenerated)
if (autoGenerated != null) {
colorPickerState.setColor(Color(autoGenerated))
}
}
) {
Icon(
Icons.Rounded.AutoFixHigh, null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text("From primary color")
}
}
}
}
}
}
}
}
@Composable
fun ThemePreferenceCategory(
title: String,
previewColorScheme: ColorScheme,
darkMode: Boolean,
onDarkModeChanged: (Boolean) -> Unit,
colorPreferences: @Composable () -> Unit = {},
preview: @Composable FlowRowScope.() -> Unit
) {
Column(
modifier = Modifier.padding(top = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f)
)
SingleChoiceSegmentedButtonRow {
SegmentedButton(
shape = SegmentedButtonDefaults.shape(position = 0, count = 2),
selected = !darkMode,
onClick = { onDarkModeChanged(false) }
) {
Icon(Icons.Rounded.LightMode, null)
}
SegmentedButton(
shape = SegmentedButtonDefaults.shape(position = 1, count = 2),
selected = darkMode,
onClick = { onDarkModeChanged(true) }
) {
Icon(Icons.Rounded.DarkMode, null)
}
}
}
MaterialTheme(
colorScheme = previewColorScheme
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
)
) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
) {
preview()
}
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(16.dp)
) {
colorPreferences()
}
HorizontalDivider()
}
}
@Composable
fun ThemeColorPreference(
title: String,
value: ThemeColor?,
corePalette: FullCorePalette,
onValueChange: (ThemeColor?) -> Unit,
defaultValue: ThemeColor,
modifier: Modifier = Modifier,
) {
var showDialog by remember { mutableStateOf(false) }
val actualValue = value ?: defaultValue
PlainTooltipBox(tooltip = { Text(title) }) {
ColorSwatch(
color = Color(actualValue.get(corePalette)),
modifier = modifier
.size(48.dp)
.tooltipTrigger()
.clickable { showDialog = true },
)
}
if (showDialog) {
BottomSheetDialog(onDismissRequest = { showDialog = false }) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(it),
) {
SingleChoiceSegmentedButtonRow(
modifier = Modifier.fillMaxWidth()
) {
SegmentedButton(
selected = actualValue is ColorRef,
onClick = {
if (actualValue is ColorRef) return@SegmentedButton
onValueChange(defaultValue)
},
icon = {
SegmentedButtonDefaults.SegmentedButtonIcon(
active = actualValue is ColorRef,
) {
Icon(
Icons.Rounded.Palette,
null,
modifier = Modifier
.size(SegmentedButtonDefaults.IconSize)
)
}
},
shape = SegmentedButtonDefaults.shape(position = 0, count = 2)
) {
Text("From palette")
}
SegmentedButton(
selected = actualValue is StaticColor,
onClick = {
onValueChange(StaticColor(actualValue.get(corePalette)))
},
icon = {
SegmentedButtonDefaults.SegmentedButtonIcon(
active = actualValue is StaticColor,
) {
Icon(
Icons.Rounded.Colorize,
null,
modifier = Modifier
.size(SegmentedButtonDefaults.IconSize)
)
}
},
shape = SegmentedButtonDefaults.shape(position = 1, count = 2)
) {
Text("Custom")
}
}
AnimatedContent(
actualValue,
label = "AnimatedContent",
contentKey = { it is StaticColor }
) { themeColor ->
Column {
if (themeColor is StaticColor) {
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(themeColor.color),
onColorChanged = {
onValueChange(StaticColor(it.toArgb()))
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
.align(Alignment.CenterHorizontally),
state = colorPickerState
)
} else if (themeColor is ColorRef) {
val hct = Hct.fromInt(corePalette.get(themeColor.color))
val hue = hct.hue.toFloat()
val chroma = hct.chroma.toFloat()
var tone by remember(value == null) { mutableStateOf(themeColor.tone.toFloat()) }
Row(
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Primary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Primary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Primary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Secondary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Secondary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Secondary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Tertiary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Tertiary,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Tertiary,
)
}
Row(
modifier = Modifier.padding(bottom = 16.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Neutral)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Neutral,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Neutral,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.NeutralVariant)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.NeutralVariant,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.NeutralVariant,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Error)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
onValueChange(
ColorRef(
CorePaletteColor.Error,
tone.roundToInt()
)
)
},
selected = themeColor.color == CorePaletteColor.Error,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Text(
text = "C",
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center
)
Slider(
modifier = Modifier.weight(1f),
value = tone,
valueRange = 0f..100f,
onValueChange = {
tone = it
onValueChange(themeColor.copy(tone = it.roundToInt()))
},
track = {
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
) {
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.hct(hue, chroma, 0f),
Color.hct(hue, chroma, 10f),
Color.hct(hue, chroma, 20f),
Color.hct(hue, chroma, 30f),
Color.hct(hue, chroma, 40f),
Color.hct(hue, chroma, 50f),
Color.hct(hue, chroma, 60f),
Color.hct(hue, chroma, 70f),
Color.hct(hue, chroma, 80f),
Color.hct(hue, chroma, 90f),
Color.hct(hue, chroma, 100f),
)
),
style = Fill,
cornerRadius = CornerRadius(
10.dp.toPx(),
10.dp.toPx()
)
)
}
},
thumb = {
Box(
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 8.dp)
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(Color.White)
)
}
)
Text(
text = tone.roundToInt().toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = { onValueChange(null) }
) {
Icon(
Icons.Rounded.RestartAlt, null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text("Restore default")
}
}
}
}
}
}
}
@Composable
fun ColorSwatch(
color: Color,
modifier: Modifier = Modifier,
selected: Boolean = false
) {
val darkTheme = LocalDarkTheme.current
val iconColor = Color(Hct.fromInt(color.toArgb()).let {
val tone = if (darkTheme) {
if (it.tone.toInt() > 40) 30f
else 60f
} else {
if (it.tone.toInt() < 60) 80f
else 40f
}
it.apply {
this.tone = tone.toDouble()
}.toInt()
})
val borderColor = Color(Hct.fromInt(color.toArgb()).let {
val tone = if (darkTheme) 30f else 80f
it.apply {
this.tone = tone.toDouble()
}.toInt()
})
Box(
modifier = modifier
.clip(CircleShape)
.border(
if (selected) 4.dp else 1.dp,
borderColor,
CircleShape
)
.background(color),
contentAlignment = Alignment.Center
) {
if (selected) {
Icon(
Icons.Rounded.CheckCircle,
null,
modifier = Modifier.size(32.dp),
tint = iconColor,
)
}
}
}