feat(ui): compose color picker
This commit is contained in:
@@ -149,6 +149,7 @@ dependencies {
|
||||
fullImplementation(libs.androidx.material3)
|
||||
fullImplementation(libs.coil.compose)
|
||||
fullImplementation(libs.coil.video)
|
||||
fullImplementation(libs.colorpicker.compose)
|
||||
fullImplementation(libs.androidx.ui.tooling.preview)
|
||||
properties["debug_flavor"]?.let {
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -244,6 +245,27 @@ class FeaturesRoot : Routes.Route() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataProcessors.Type.INT_COLOR -> {
|
||||
dialogComposable = {
|
||||
alertDialogs.ColorPickerDialog(property) {
|
||||
showDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
registerDialogOnClickCallback().let { { it.invoke(true) } }.also {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.border(2.dp, Color.White, shape = RoundedCornerShape(15.dp))
|
||||
.background(
|
||||
color = (propertyValue.getNullable() as? Int)?.let { Color(it) } ?: Color.Transparent,
|
||||
shape = RoundedCornerShape(15.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DataProcessors.Type.CONTAINER -> {
|
||||
val container = propertyValue.get() as ConfigContainer
|
||||
|
||||
|
||||
@@ -6,17 +6,22 @@ import android.widget.Toast
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextRange
|
||||
@@ -26,6 +31,11 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.github.skydoves.colorpicker.compose.AlphaSlider
|
||||
import com.github.skydoves.colorpicker.compose.AlphaTile
|
||||
import com.github.skydoves.colorpicker.compose.BrightnessSlider
|
||||
import com.github.skydoves.colorpicker.compose.HsvColorPicker
|
||||
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
|
||||
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.common.config.DataProcessors
|
||||
import me.rhunk.snapenhance.common.config.PropertyPair
|
||||
@@ -327,6 +337,104 @@ class AlertDialogs(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColorPickerDialog(
|
||||
property: PropertyPair<*>,
|
||||
dismiss: () -> Unit = {},
|
||||
) {
|
||||
var currentColor by remember {
|
||||
mutableStateOf((property.value.getNullable() as? Int)?.let { Color(it) })
|
||||
}
|
||||
|
||||
DefaultDialogCard {
|
||||
val controller = rememberColorPickerController()
|
||||
var colorHexValue by remember {
|
||||
mutableStateOf(currentColor?.toArgb()?.let { Integer.toHexString(it) } ?: "")
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
TextField(
|
||||
value = colorHexValue,
|
||||
onValueChange = { value ->
|
||||
colorHexValue = value
|
||||
runCatching {
|
||||
currentColor = Color(android.graphics.Color.parseColor("#$value")).also {
|
||||
controller.selectByColor(it, true)
|
||||
property.value.setAny(it.toArgb())
|
||||
}
|
||||
}.onFailure {
|
||||
currentColor = null
|
||||
}
|
||||
},
|
||||
label = { Text(text = "Hex Color") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
singleLine = true,
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
focusedContainerColor = Color.Transparent,
|
||||
)
|
||||
)
|
||||
}
|
||||
HsvColorPicker(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(300.dp)
|
||||
.padding(10.dp),
|
||||
initialColor = remember { currentColor },
|
||||
controller = controller,
|
||||
onColorChanged = {
|
||||
if (!it.fromUser) return@HsvColorPicker
|
||||
currentColor = it.color
|
||||
colorHexValue = Integer.toHexString(it.color.toArgb())
|
||||
property.value.setAny(it.color.toArgb())
|
||||
}
|
||||
)
|
||||
AlphaSlider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
.height(35.dp),
|
||||
controller = controller,
|
||||
)
|
||||
BrightnessSlider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
.height(35.dp),
|
||||
controller = controller,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AlphaTile(
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.clip(RoundedCornerShape(6.dp)),
|
||||
controller = controller
|
||||
)
|
||||
IconButton(onClick = {
|
||||
property.value.setAny(null)
|
||||
dismiss()
|
||||
}) {
|
||||
Icon(
|
||||
modifier = Modifier.size(60.dp),
|
||||
imageVector = Icons.Filled.DeleteOutline,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChooseLocationDialog(property: PropertyPair<*>, dismiss: () -> Unit = {}) {
|
||||
val coordinates = remember {
|
||||
|
||||
@@ -68,6 +68,12 @@ open class ConfigContainer(
|
||||
params: ConfigParamsBuilder = {}
|
||||
) = registerProperty(key, DataProcessors.MAP_COORDINATES, PropertyValue(defaultValue), params)
|
||||
|
||||
protected fun color(
|
||||
key: String,
|
||||
defaultValue: Int? = null,
|
||||
params: ConfigParamsBuilder = {}
|
||||
) = registerProperty(key, DataProcessors.INT_COLOR, PropertyValue(defaultValue), params)
|
||||
|
||||
fun toJson(): JsonObject {
|
||||
val json = JsonObject()
|
||||
properties.forEach { (propertyKey, propertyValue) ->
|
||||
|
||||
@@ -15,6 +15,7 @@ object DataProcessors {
|
||||
STRING_MULTIPLE_SELECTION,
|
||||
STRING_UNIQUE_SELECTION,
|
||||
MAP_COORDINATES,
|
||||
INT_COLOR,
|
||||
CONTAINER,
|
||||
}
|
||||
|
||||
@@ -91,6 +92,14 @@ object DataProcessors {
|
||||
},
|
||||
)
|
||||
|
||||
val INT_COLOR = PropertyDataProcessor(
|
||||
type = Type.INT_COLOR,
|
||||
serialize = {
|
||||
it?.let { JsonPrimitive(it) } ?: JsonNull.INSTANCE
|
||||
},
|
||||
deserialize = { if (it.isJsonNull) null else it.asString.toIntOrNull() },
|
||||
)
|
||||
|
||||
fun <T : ConfigContainer> container(container: T) = PropertyDataProcessor(
|
||||
type = Type.CONTAINER,
|
||||
serialize = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.rhunk.snapenhance.common.config.impl
|
||||
|
||||
import android.graphics.Color
|
||||
import me.rhunk.snapenhance.common.config.ConfigContainer
|
||||
import me.rhunk.snapenhance.common.config.FeatureNotice
|
||||
import me.rhunk.snapenhance.common.data.MessagingRuleType
|
||||
@@ -20,16 +19,13 @@ class UserInterfaceTweaks : ConfigContainer() {
|
||||
}
|
||||
|
||||
inner class CustomizeUIConfig : ConfigContainer(hasGlobalState = true) {
|
||||
private val checkInputColor = { value: String ->
|
||||
value.isEmpty() || runCatching { Color.parseColor(value) }.isSuccess
|
||||
}
|
||||
val textColor = string("text_color") { inputCheck = checkInputColor }
|
||||
val sendAndReceivedTextColor = string("send_and_received_text_color") { inputCheck = checkInputColor }
|
||||
val backgroundColor = string("background_color") { inputCheck = checkInputColor }
|
||||
val backgroundColorSurface = string("background_color_surface") { inputCheck = checkInputColor }
|
||||
val actionMenuBackgroundColor = string("action_menu_background_color") { inputCheck = checkInputColor }
|
||||
val actionMenuRoundBackgroundColor = string("action_menu_round_background_color") { inputCheck = checkInputColor }
|
||||
val cameraGridLines = string("camera_grid_lines") { inputCheck = checkInputColor }
|
||||
val textColor = color("text_color")
|
||||
val sendAndReceivedTextColor = color("send_and_received_text_color")
|
||||
val backgroundColor = color("background_color")
|
||||
val backgroundColorSurface = color("background_color_surface")
|
||||
val actionMenuBackgroundColor = color("action_menu_background_color")
|
||||
val actionMenuRoundBackgroundColor = color("action_menu_round_background_color")
|
||||
val cameraGridLines = color("camera_grid_lines")
|
||||
}
|
||||
|
||||
val friendFeedMenuButtons = multiple(
|
||||
|
||||
@@ -25,13 +25,13 @@ class CustomizeUI: Feature("Customize UI", loadParams = FeatureLoadParams.ACTIVI
|
||||
|
||||
//TODO: color picker
|
||||
val customizeUIConfig = context.config.userInterface.customizeUi
|
||||
val effectiveTextColor by lazy { parseColor(customizeUIConfig.textColor.get()) }
|
||||
val effectivesendAndReceivedTextColor by lazy { parseColor(customizeUIConfig.sendAndReceivedTextColor.get()) }
|
||||
val effectiveBackgroundColor by lazy { parseColor(customizeUIConfig.backgroundColor.get()) }
|
||||
val effectiveBackgroundColorSurface by lazy { parseColor(customizeUIConfig.backgroundColorSurface.get()) }
|
||||
val effectiveActionMenuBackgroundColor by lazy { parseColor(customizeUIConfig.actionMenuBackgroundColor.get()) }
|
||||
val effectiveActionMenuRoundBackgroundColor by lazy { parseColor(customizeUIConfig.actionMenuRoundBackgroundColor.get()) }
|
||||
val effectiveCameraGridLines by lazy { parseColor(customizeUIConfig.cameraGridLines.get()) }
|
||||
val effectiveTextColor = customizeUIConfig.textColor.getNullable()
|
||||
val effectivesendAndReceivedTextColor = customizeUIConfig.sendAndReceivedTextColor.getNullable()
|
||||
val effectiveBackgroundColor = customizeUIConfig.backgroundColor.getNullable()
|
||||
val effectiveBackgroundColorSurface = customizeUIConfig.backgroundColorSurface.getNullable()
|
||||
val effectiveActionMenuBackgroundColor = customizeUIConfig.actionMenuBackgroundColor.getNullable()
|
||||
val effectiveActionMenuRoundBackgroundColor = customizeUIConfig.actionMenuRoundBackgroundColor.getNullable()
|
||||
val effectiveCameraGridLines = customizeUIConfig.cameraGridLines.getNullable()
|
||||
|
||||
|
||||
val attributeCache = mutableMapOf<String, Int>()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[versions]
|
||||
agp = "8.2.2"
|
||||
apksig = "8.2.2"
|
||||
colorpicker-compose = "1.0.7"
|
||||
libsu = "5.2.2"
|
||||
guava = "33.0.0-jre"
|
||||
jsoup = "1.17.2"
|
||||
@@ -42,6 +43,7 @@ apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||
bcprov-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcprov-jdk18on" }
|
||||
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" }
|
||||
coil-video = { module = "io.coil-kt:coil-video", version.ref = "coil-compose" }
|
||||
colorpicker-compose = { module = "com.github.skydoves:colorpicker-compose", version.ref = "colorpicker-compose" }
|
||||
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||
coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" }
|
||||
dexlib2 = { group = "com.android.tools.smali", name = "smali-dexlib2", version.ref = "dexlib2" }
|
||||
|
||||
Reference in New Issue
Block a user