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:
Mapp/build.gradle.kts | 1+
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt | 22++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigContainer.kt | 6++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/DataProcessors.kt | 9+++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt | 18+++++++-----------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt | 14+++++++-------
Mgradle/libs.versions.toml | 2++
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" }