feat(IconOpts): add lightness slider & improve ui

This commit is contained in:
rushiiMachine 2025-03-30 00:25:53 -07:00
parent b5beb8608f
commit 6427c19783
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
6 changed files with 212 additions and 25 deletions

View file

@ -0,0 +1,113 @@
/**
* MIT License
*
* Copyright (c) 2025 zt64
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// Adapted from compose-pipette:
// https://github.com/zt64/compose-pipette/blob/3e9fd958a315dceb142bf30250b4614ecde4e723/sample/src/commonMain/kotlin/dev/zt64/compose/pipette/sample/SampleSlider.kt
package com.aliucord.manager.ui.components
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.*
import androidx.compose.ui.unit.dp
@Composable
fun InteractiveSlider(
value: Float,
onValueChange: (Float) -> Unit,
valueRange: ClosedFloatingPointRange<Float>,
brush: Brush,
thumbColor: Color = MaterialTheme.colorScheme.primary,
modifier: Modifier = Modifier,
) {
val interactionSource = remember(::MutableInteractionSource)
Slider(
value = value,
onValueChange = onValueChange,
thumb = {
val interactions = remember { mutableStateListOf<Interaction>() }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> interactions.add(interaction)
is PressInteraction.Release -> interactions.remove(interaction.press)
is PressInteraction.Cancel -> interactions.remove(interaction.press)
is DragInteraction.Start -> interactions.add(interaction)
is DragInteraction.Stop -> interactions.remove(interaction.start)
is DragInteraction.Cancel -> interactions.remove(interaction.start)
}
}
}
Box(
modifier = Modifier
.size(28.dp)
.hoverable(interactionSource = interactionSource),
) {
val visualSize by remember {
derivedStateOf {
if (interactions.isNotEmpty()) 28.dp else 24.dp
}
}
Spacer(
modifier = Modifier
.size(visualSize)
.align(Alignment.Center)
.background(thumbColor, CircleShape),
)
}
},
track = {
Canvas(
modifier = Modifier
.widthIn(max = 700.dp)
.height(12.dp)
.fillMaxWidth(),
) {
drawLine(
brush = brush,
start = Offset(0f, size.center.y),
end = Offset(size.width, size.center.y),
strokeWidth = size.height,
cap = StrokeCap.Round,
)
}
},
valueRange = valueRange,
interactionSource = interactionSource,
modifier = modifier,
)
}

View file

@ -1,8 +1,7 @@
package com.aliucord.manager.ui.screens.home.components
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@ -73,6 +72,7 @@ fun InstallButton(
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(end = 10.dp),
) {
Icon(
painter = painterResource(R.drawable.ic_add),

View file

@ -0,0 +1,27 @@
package com.aliucord.manager.ui.screens.iconopts
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import dev.zt64.compose.pipette.util.*
@Stable
class HSVColorState(
hue: Float,
saturation: Float,
value: Float,
) {
var hue by mutableFloatStateOf(hue)
var saturation by mutableFloatStateOf(saturation)
var value by mutableFloatStateOf(value)
fun toARGB(): Color = Color.hsv(hue, saturation, value)
override fun toString(): String = "HSVColorState($hue, $saturation, $value)"
}
fun Color.toHSVState(): HSVColorState {
return HSVColorState(
hue = this.hue,
saturation = this.saturation,
value = this.hsvValue,
)
}

View file

@ -0,0 +1,11 @@
package com.aliucord.manager.ui.screens.iconopts
import androidx.compose.runtime.Immutable
@Immutable
enum class IconOptionsMode {
Original,
Aliucord,
CustomColor,
CustomImage,
}

View file

@ -17,12 +17,11 @@ class IconOptionsModel(
}
// ---------- Replacement color ---------- //
var selectedColorValue by mutableLongStateOf(0)
val selectedColor: Color
inline get() = Color(selectedColorValue.toULong())
var selectedColor by mutableStateOf<HSVColorState>(IconReplacement.Aliucord.color.toHSVState())
private set
fun changeSelectedColor(color: Color) {
selectedColorValue = color.value.toLong()
fun initSelectedColor(color: Color) {
selectedColor = color.toHSVState()
}
// ---------- Other ---------- //
@ -30,12 +29,12 @@ class IconOptionsModel(
when (prefilledOptions) {
is IconReplacement.CustomColor if prefilledOptions.color == IconReplacement.Aliucord.color -> {
changeMode(IconOptionsMode.Aliucord)
changeSelectedColor(IconReplacement.Aliucord.color)
initSelectedColor(IconReplacement.Aliucord.color)
}
is IconReplacement.CustomColor -> {
changeMode(IconOptionsMode.CustomColor)
changeSelectedColor(prefilledOptions.color)
initSelectedColor(prefilledOptions.color)
}
is IconReplacement.CustomImage -> {
@ -50,14 +49,7 @@ class IconOptionsModel(
fun generateConfig(): IconReplacement = when (mode) {
IconOptionsMode.Original -> IconReplacement.Original
IconOptionsMode.Aliucord -> IconReplacement.Aliucord
IconOptionsMode.CustomColor -> IconReplacement.CustomColor(color = selectedColor)
IconOptionsMode.CustomColor -> IconReplacement.CustomColor(color = selectedColor.toARGB())
IconOptionsMode.CustomImage -> TODO()
}
}
enum class IconOptionsMode {
Original,
Aliucord,
CustomColor,
CustomImage,
}

View file

@ -11,13 +11,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import com.aliucord.manager.R
import com.aliucord.manager.ui.components.InteractiveSlider
import com.aliucord.manager.ui.screens.iconopts.components.IconOptionsAppBar
import com.aliucord.manager.ui.screens.iconopts.components.RadioSelectorItem
import com.aliucord.manager.ui.screens.patchopts.components.options.PatchOption
import dev.zt64.compose.pipette.CircularColorPicker
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -46,7 +49,6 @@ class IconOptionsScreen(
mode = model.mode,
setMode = model::changeMode,
selectedColor = model.selectedColor,
setSelectedColor = model::changeSelectedColor,
)
}
}
@ -55,8 +57,7 @@ class IconOptionsScreen(
fun IconOptionsScreenContent(
mode: IconOptionsMode,
setMode: (IconOptionsMode) -> Unit,
selectedColor: Color,
setSelectedColor: (Color) -> Unit,
selectedColor: HSVColorState,
) {
Scaffold(
topBar = { IconOptionsAppBar() },
@ -100,12 +101,55 @@ fun IconOptionsScreenContent(
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically { -it / 10 },
) {
CircularColorPicker(
color = selectedColor,
onColorChange = setSelectedColor,
modifier = Modifier.size(230.dp),
)
CustomColorOptions(selectedColor)
}
}
}
}
@Composable
private fun CustomColorOptions(color: HSVColorState) {
Column(
verticalArrangement = Arrangement.spacedBy(30.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 15.dp)
) {
PatchOption(
name = "Hue & Saturation",
description = "The main color components",
modifier = Modifier.fillMaxWidth()
) {
CircularColorPicker(
hue = color.hue,
saturation = color.saturation,
value = color.value,
onColorChange = { hue, saturation ->
color.hue = hue
color.saturation = saturation
},
modifier = Modifier
.padding(top = 20.dp)
.size(260.dp)
.align(Alignment.CenterHorizontally)
)
}
PatchOption(
name = "Lightness",
description = "The brightness component of the color",
) {
InteractiveSlider(
value = color.value,
onValueChange = { color.value = it },
valueRange = 0f..1f,
brush = Brush.horizontalGradient(
listOf(
Color.Black,
Color.hsv(color.hue, color.saturation, 1f)
),
),
thumbColor = color.toARGB(),
)
}
}
}