Split custom color scheme preferences to multiple files
This commit is contained in:
parent
68df2cc89e
commit
cf6cbf20f4
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,47 +1,22 @@
|
|||||||
package de.mm20.launcher2.ui.settings.colorscheme
|
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.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
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.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.PlayArrow
|
||||||
import androidx.compose.material.icons.rounded.RestartAlt
|
|
||||||
import androidx.compose.material.icons.rounded.Search
|
import androidx.compose.material.icons.rounded.Search
|
||||||
import androidx.compose.material.icons.rounded.SettingsSuggest
|
|
||||||
import androidx.compose.material.icons.rounded.Tag
|
import androidx.compose.material.icons.rounded.Tag
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ColorScheme
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
@ -53,11 +28,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.OutlinedTextField
|
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.Snackbar
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -71,16 +41,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.ColorLayer
|
||||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||||
import de.mm20.launcher2.icons.TintedIconLayer
|
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.DefaultDarkColorScheme
|
||||||
import de.mm20.launcher2.themes.DefaultLightColorScheme
|
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.themes.merge
|
||||||
import de.mm20.launcher2.ui.R
|
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.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.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.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
|
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
|
||||||
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
|
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
|
||||||
import de.mm20.launcher2.ui.theme.colorscheme.systemCorePalette
|
import de.mm20.launcher2.ui.theme.colorscheme.systemCorePalette
|
||||||
import hct.Hct
|
|
||||||
import palettes.CorePalette
|
import palettes.CorePalette
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import de.mm20.launcher2.themes.Color as ThemeColor
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemeSettingsScreen(themeId: UUID) {
|
fun ThemeSettingsScreen(themeId: UUID) {
|
||||||
@ -986,26 +934,26 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Surface Variant",
|
title = "On Surface Variant",
|
||||||
value = selectedColorScheme.surfaceVariant,
|
value = selectedColorScheme.onSurfaceVariant,
|
||||||
corePalette = mergedCorePalette,
|
corePalette = mergedCorePalette,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
viewModel.updateTheme(
|
viewModel.updateTheme(
|
||||||
if (previewDarkTheme) {
|
if (previewDarkTheme) {
|
||||||
theme!!.copy(
|
theme!!.copy(
|
||||||
darkColorScheme = theme!!.darkColorScheme.copy(
|
darkColorScheme = theme!!.darkColorScheme.copy(
|
||||||
surfaceVariant = it
|
onSurfaceVariant = it
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
theme!!.copy(
|
theme!!.copy(
|
||||||
lightColorScheme = theme!!.lightColorScheme.copy(
|
lightColorScheme = theme!!.lightColorScheme.copy(
|
||||||
surfaceVariant = it
|
onSurfaceVariant = it
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceVariant,
|
defaultValue = selectedDefaultScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
modifier = Modifier.padding(end = 12.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1098,7 +1046,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
) {}
|
) {}
|
||||||
VerticalDivider(
|
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user