feat: 自定义主题色支持直接输入颜色的十六进制

This commit is contained in:
HuanCheng65 2024-02-01 18:02:07 +08:00
parent b18571c1e5
commit fe0c667af5
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
4 changed files with 169 additions and 38 deletions

View File

@ -1,6 +1,8 @@
package com.huanchengfly.tieba.post.ui.page.settings.theme package com.huanchengfly.tieba.post.ui.page.settings.theme
import android.os.Build import android.os.Build
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -15,17 +17,24 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Checkbox import androidx.compose.material.Checkbox
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.BorderColor
import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.ColorLens import androidx.compose.material.icons.rounded.ColorLens
import androidx.compose.material.icons.rounded.Colorize import androidx.compose.material.icons.rounded.Colorize
@ -34,6 +43,7 @@ import androidx.compose.material.icons.rounded.PhotoSizeSelectActual
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -47,7 +57,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import com.github.panpf.sketch.compose.AsyncImage import com.github.panpf.sketch.compose.AsyncImage
import com.github.panpf.sketch.fetch.newFileUri import com.github.panpf.sketch.fetch.newFileUri
@ -74,6 +86,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
import com.huanchengfly.tieba.post.utils.ThemeUtil import com.huanchengfly.tieba.post.utils.ThemeUtil
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import com.huanchengfly.tieba.post.utils.extension.toHexString
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@ -119,21 +132,95 @@ fun AppThemePage(
dialogState = customPrimaryColorDialogState, dialogState = customPrimaryColorDialogState,
title = { Text(text = stringResource(id = R.string.title_custom_theme)) }, title = { Text(text = stringResource(id = R.string.title_custom_theme)) },
content = { content = {
var useInput by remember { mutableStateOf(false) }
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
HarmonyColorPicker( AnimatedContent(
harmonyMode = ColorHarmonyMode.ANALOGOUS, targetState = useInput,
color = HsvColor.from(customPrimaryColor), label = "",
onColorChanged = { modifier = Modifier
customPrimaryColor = it.toColor() .wrapContentHeight()
}, .animateContentSize()
modifier = Modifier.size(350.dp) ) { input ->
) if (input) {
var inputHexColor by remember { mutableStateOf(customPrimaryColor.toHexString()) }
val lastValidColor by produceState(
initialValue = customPrimaryColor,
inputHexColor
) {
if ("^#([0-9a-fA-F]{6})$".toRegex().matches(inputHexColor)) {
value = Color(inputHexColor.toColorInt())
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth()
) {
Spacer(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(lastValidColor)
)
OutlinedTextField(
value = inputHexColor,
onValueChange = {
if ("^#([0-9a-fA-F]{0,6})$".toRegex().matches(it)) {
inputHexColor = it
}
},
maxLines = 1,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
modifier = Modifier.weight(1f),
colors = TextFieldDefaults.outlinedTextFieldColors(
cursorColor = ExtendedTheme.colors.primary,
focusedBorderColor = ExtendedTheme.colors.primary,
focusedLabelColor = ExtendedTheme.colors.primary
)
)
IconButton(
onClick = {
customPrimaryColor = Color(inputHexColor.toColorInt())
useInput = false
}
) {
Icon(
imageVector = Icons.Rounded.Check,
contentDescription = stringResource(id = R.string.button_sure_default)
)
}
}
} else {
Box {
HarmonyColorPicker(
harmonyMode = ColorHarmonyMode.ANALOGOUS,
color = HsvColor.from(customPrimaryColor),
onColorChanged = {
customPrimaryColor = it.toColor()
},
modifier = Modifier.sizeIn(maxWidth = 320.dp, maxHeight = 320.dp)
)
IconButton(
onClick = { useInput = true },
modifier = Modifier.align(Alignment.TopEnd)
) {
Icon(
imageVector = Icons.Rounded.BorderColor,
contentDescription = stringResource(id = R.string.desc_input_color)
)
}
}
}
}
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,

View File

@ -1,5 +1,6 @@
package com.huanchengfly.tieba.post.ui.widgets.compose package com.huanchengfly.tieba.post.ui.widgets.compose
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -29,7 +31,6 @@ import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@ -39,6 +40,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
@ -239,7 +243,6 @@ fun ConfirmDialog(
* *
* @param onValueChange 输入框内容变化时的回调返回true表示允许变化false表示不允许变化 * @param onValueChange 输入框内容变化时的回调返回true表示允许变化false表示不允许变化
*/ */
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun PromptDialog( fun PromptDialog(
onConfirm: (String) -> Unit, onConfirm: (String) -> Unit,
@ -361,40 +364,72 @@ fun Dialog(
dismissOnClickOutside = cancelableOnTouchOutside dismissOnClickOutside = cancelableOnTouchOutside
) )
) { ) {
Column( ProvideContentColor(color = ExtendedTheme.colors.text) {
modifier = modifier ConstraintLayout(
.fillMaxWidth( modifier = modifier
fraction = if (windowWidthSizeClass == WindowWidthSizeClass.Compact) { .wrapContentHeight()
1f .animateContentSize()
} else { .fillMaxWidth(
0.6f fraction = if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
} 1f
) } else {
.padding(16.dp) 0.6f
.background( }
color = ExtendedTheme.colors.windowBackground, )
shape = RoundedCornerShape(24.dp) .padding(16.dp)
) .background(
.padding(vertical = 24.dp), color = ExtendedTheme.colors.windowBackground,
verticalArrangement = Arrangement.spacedBy(16.dp) shape = RoundedCornerShape(24.dp)
) { )
ProvideContentColor(color = ExtendedTheme.colors.text) { .padding(vertical = 24.dp),
if (title != null) { ) {
Box( val (titleRef, contentRef, buttonsRef) = createRefs()
modifier = Modifier Column(
.padding(horizontal = 24.dp) modifier = Modifier
.align(Alignment.CenterHorizontally) .constrainAs(titleRef) {
) { top.linkTo(parent.top)
ProvideTextStyle(value = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold)) { start.linkTo(parent.start)
dialogScope.title() end.linkTo(parent.end)
width = Dimension.fillToConstraints
visibility = if (title == null) {
Visibility.Gone
} else {
Visibility.Visible
}
}
) {
if (title != null) {
Box(
modifier = Modifier
.padding(horizontal = 24.dp)
.align(Alignment.CenterHorizontally)
) {
ProvideTextStyle(value = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold)) {
dialogScope.title()
}
} }
} }
} }
Box(modifier = Modifier.align(Alignment.CenterHorizontally)) { Column(
modifier = Modifier
.constrainAs(contentRef) {
top.linkTo(titleRef.bottom, margin = 16.dp, goneMargin = 0.dp)
bottom.linkTo(buttonsRef.top, margin = 16.dp, goneMargin = 0.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
height = Dimension.preferredWrapContent
}
) {
dialogScope.content() dialogScope.content()
} }
Column( Column(
modifier = Modifier modifier = Modifier
.constrainAs(buttonsRef) {
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
}
.padding(horizontal = 24.dp), .padding(horizontal = 24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {

View File

@ -0,0 +1,8 @@
package com.huanchengfly.tieba.post.utils.extension
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
fun Color.toHexString(): String {
return "#${Integer.toHexString(toArgb()).substring(2)}"
}

View File

@ -517,4 +517,5 @@
<string name="desc_show">显示</string> <string name="desc_show">显示</string>
<string name="title_settings_status_bar_darker">状态栏遮罩</string> <string name="title_settings_status_bar_darker">状态栏遮罩</string>
<string name="summary_settings_status_bar_darker">开启后非白色主题状态栏将略微变暗</string> <string name="summary_settings_status_bar_darker">开启后非白色主题状态栏将略微变暗</string>
<string name="desc_input_color">手动输入颜色</string>
</resources> </resources>