feat(ui): compose color picker

This commit is contained in:
rhunk
2024-05-08 12:41:11 +02:00
parent eb7fbaff6f
commit c4b0f79630
8 changed files with 162 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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