commit c4b0f79630cada18407f645dd1585acc513ba4f8
parent eb7fbaff6fc700938e50133f154db3bd26247a6d
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Wed, 8 May 2024 12:41:11 +0200
feat(ui): compose color picker
Diffstat:
8 files changed, 162 insertions(+), 18 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
@@ -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)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt
@@ -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
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt
@@ -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
@@ -328,6 +338,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 {
(property.value.get() as Pair<*, *>).let {
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigContainer.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigContainer.kt
@@ -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) ->
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/DataProcessors.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/DataProcessors.kt
@@ -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 = {
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt
@@ -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(
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt
@@ -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>()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
@@ -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" }