feat(settings): 添加颜色对比度设置项

This commit is contained in:
Super12138 2025-02-05 16:26:32 +08:00
parent 7ab13cd6f9
commit 121334498c
13 changed files with 221 additions and 13 deletions

View file

@ -11,4 +11,7 @@ object Constants {
const val PREF_DARK_MODE = "dark_mode"
const val PREF_DARK_MODE_DEFAULT = 1 // Follow System
const val PREF_CONTRAST_LEVEL = "contrast_level"
const val PREF_CONTRAST_LEVEL_DEFAULT = 0f // Normal
}

View file

@ -15,4 +15,8 @@ object GlobalValues {
Constants.PREF_DARK_MODE,
Constants.PREF_DARK_MODE_DEFAULT
)
var contrastLevel: Float by SPDelegates(
Constants.PREF_CONTRAST_LEVEL,
Constants.PREF_CONTRAST_LEVEL_DEFAULT
)
}

View file

@ -0,0 +1,29 @@
package cn.super12138.todo.logic.model
import android.content.Context
import cn.super12138.todo.R
enum class ContrastLevel(val value: Float) {
VeryLow(-1f),
Low(-0.5f),
Default(0f),
Medium(0.5f),
High(1f);
fun getDisplayName(context: Context): String {
val resId = when (this) {
VeryLow -> R.string.contrast_very_low
Low -> R.string.contrast_low
Default -> R.string.contrast_default
Medium -> R.string.contrast_medium
High -> R.string.contrast_high
}
return context.getString(resId)
}
companion object {
fun fromFloat(float: Float): ContrastLevel {
return ContrastLevel.entries.find { it.value == float } ?: Default
}
}
}

View file

@ -27,11 +27,13 @@ class MainActivity : ComponentActivity() {
setContent {
val mainViewModel: MainViewModel = viewModel()
val showConfetti = mainViewModel.showConfetti
// 深色模式
val darkTheme = when (mainViewModel.appDarkMode) {
FollowSystem -> isSystemInDarkTheme()
Light -> false
Dark -> true
}
// 配置状态栏和底部导航栏的颜色(在用户切换深色模式时)
// https://github.com/dn0ne/lotus/blob/master/app/src/main/java/com/dn0ne/player/MainActivity.kt#L266
LaunchedEffect(mainViewModel.appDarkMode) {
WindowCompat.getInsetsController(window, window.decorView).apply {
@ -39,7 +41,11 @@ class MainActivity : ComponentActivity() {
isAppearanceLightNavigationBars = !darkTheme
}
}
ToDoTheme(darkTheme = darkTheme) {
ToDoTheme(
darkTheme = darkTheme,
contrastLevel = mainViewModel.appContrastLevel.value.toDouble()
) {
Surface(color = MaterialTheme.colorScheme.background) {
TodoNavigation(
viewModel = mainViewModel,

View file

@ -17,6 +17,7 @@ import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
import cn.super12138.todo.ui.pages.settings.components.contrast.ContrastPicker
import cn.super12138.todo.ui.pages.settings.components.darkmode.DarkModePicker
import cn.super12138.todo.ui.pages.settings.components.palette.PalettePicker
import cn.super12138.todo.ui.theme.appPaletteStyle
@ -45,7 +46,12 @@ fun SettingsAppearance(
) {
DarkModePicker(onDarkModeChange = { viewModel.setDarkMode(it) })
PalettePicker(onPaletteChange = { appPaletteStyle = it })
PalettePicker(
contrastLevel = viewModel.appContrastLevel,
onPaletteChange = { appPaletteStyle = it }
)
ContrastPicker(onContrastChange = { viewModel.setContrastLevel(it) })
SwitchSettingsItem(
key = Constants.PREF_DYNAMIC_COLOR,

View file

@ -1,8 +1,10 @@
package cn.super12138.todo.ui.pages.settings.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
@ -40,6 +42,34 @@ fun RowSettingsItem(
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyListScope.() -> Unit
) {
MoreContentSettingsItem(
title = title,
description = description,
shape = shape,
modifier = modifier
) {
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = state,
contentPadding = contentPadding,
reverseLayout = reverseLayout,
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
flingBehavior = flingBehavior,
userScrollEnabled = userScrollEnabled,
content = content
)
}
}
@Composable
fun MoreContentSettingsItem(
title: String,
description: String? = null,
shape: Shape = MaterialTheme.shapes.large,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(
modifier = modifier
@ -70,16 +100,12 @@ fun RowSettingsItem(
Spacer(Modifier.size(8.dp))
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = state,
contentPadding = contentPadding,
reverseLayout = reverseLayout,
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
flingBehavior = flingBehavior,
userScrollEnabled = userScrollEnabled,
content = content
)
/*Box(
Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.surfaceContainer)
) {*/
content()
}
}

View file

@ -0,0 +1,75 @@
package cn.super12138.todo.ui.pages.settings.components.contrast
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Label
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem
import cn.super12138.todo.ui.pages.settings.state.rememberPrefFloatState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ContrastPicker(
onContrastChange: (ContrastLevel) -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
MoreContentSettingsItem(
title = stringResource(R.string.pref_contrast_level),
description = stringResource(R.string.pref_contrast_level_desc)
) {
var contrastState by rememberPrefFloatState(
Constants.PREF_CONTRAST_LEVEL,
Constants.PREF_CONTRAST_LEVEL_DEFAULT
)
val interactionSource = remember { MutableInteractionSource() }
Slider(
modifier = Modifier.semantics {
contentDescription = context.getString(R.string.tip_change_contrast_level)
},
value = contrastState,
onValueChange = {
contrastState = it
onContrastChange(ContrastLevel.fromFloat(it))
},
valueRange = -1f..1f,
steps = 3,
interactionSource = interactionSource,
thumb = {
Label(
label = {
PlainTooltip(
modifier = Modifier
.sizeIn(45.dp, 25.dp)
.wrapContentWidth()
) {
Text(ContrastLevel.fromFloat(contrastState).getDisplayName(context))
}
},
interactionSource = interactionSource
) {
SliderDefaults.Thumb(interactionSource)
}
}
)
}
}

View file

@ -28,12 +28,14 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import cn.super12138.todo.constants.GlobalValues
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.ui.theme.PaletteStyle
import cn.super12138.todo.ui.theme.dynamicColorScheme
@Composable
fun PaletteItem(
paletteStyle: PaletteStyle,
contrastLevel: ContrastLevel,
selected: Boolean,
onSelect: () -> Unit,
modifier: Modifier = Modifier
@ -58,6 +60,7 @@ fun PaletteItem(
Color(0xFF0061A4)
},
isDark = isSystemInDarkTheme(),
contrastLevel = contrastLevel.value.toDouble(),
style = paletteStyle
)
) {

View file

@ -11,12 +11,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.ui.pages.settings.components.RowSettingsItem
import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState
import cn.super12138.todo.ui.theme.PaletteStyle
@Composable
fun PalettePicker(
contrastLevel: ContrastLevel,
onPaletteChange: (paletteStyle: PaletteStyle) -> Unit,
modifier: Modifier = Modifier
) {
@ -38,6 +40,7 @@ fun PalettePicker(
PaletteItem(
paletteStyle = paletteStyle,
selected = PaletteStyle.fromId(paletteState) == paletteStyle,
contrastLevel = contrastLevel,
onSelect = {
paletteState = paletteStyle.id
onPaletteChange(paletteStyle)

View file

@ -3,8 +3,10 @@ package cn.super12138.todo.ui.pages.settings.state
import android.content.Context
import android.content.SharedPreferences
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableFloatState
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -28,6 +30,12 @@ fun rememberPrefIntState(key: String, default: Int): MutableIntState {
return remember { PrefMutableIntState(context, key, default) }
}
@Composable
fun rememberPrefFloatState(key: String, default: Float): MutableFloatState {
val context = LocalContext.current
return remember { PrefMutableFloatState(context, key, default) }
}
private class PrefMutableBooleanState(
val context: Context,
val key: String,
@ -74,6 +82,29 @@ private class PrefMutableIntState(
}
}
private class PrefMutableFloatState(
val context: Context,
val key: String,
default: Float,
) : MutableFloatState {
private val delegate = mutableFloatStateOf(context.pref.getFloat(key, default))
override var floatValue: Float
get() = delegate.floatValue
set(value) {
delegate.floatValue = value
context.pref.edit().putFloat(key, value).apply()
}
override fun component1(): Float {
return delegate.component1()
}
override fun component2(): (Float) -> Unit {
return delegate.component2()
}
}
/**
* 来自https://github.com/hushenghao/AndroidEasterEggs/blob/main/basic/src/main/java/com/dede/android_eggs/util/Pref.kt
* @author hushenghao

View file

@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import cn.super12138.todo.constants.GlobalValues
import cn.super12138.todo.logic.Repository
import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.logic.model.DarkMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@ -26,6 +27,8 @@ class MainViewModel : ViewModel() {
// 深色模式
var appDarkMode by mutableStateOf(DarkMode.fromId(GlobalValues.darkMode))
private set
var appContrastLevel by mutableStateOf(ContrastLevel.fromFloat(GlobalValues.contrastLevel))
private set
// 多选逻辑参考https://github.com/X1nto/Mauth
private val _selectedTodoIds = MutableStateFlow(listOf<Int>())
@ -118,4 +121,8 @@ class MainViewModel : ViewModel() {
fun setDarkMode(darkMode: DarkMode) {
appDarkMode = darkMode
}
fun setContrastLevel(contrastLevel: ContrastLevel) {
appContrastLevel = contrastLevel
}
}

View file

@ -66,4 +66,11 @@
<string name="dark_mode_system">跟随系统</string>
<string name="dark_mode_light">浅色模式</string>
<string name="dark_mode_dark">深色模式</string>
<string name="pref_contrast_level">对比度</string>
<string name="pref_contrast_level_desc">调整应用的颜色对比度</string>
<string name="contrast_very_low">非常低</string>
<string name="contrast_low"></string>
<string name="contrast_default">默认</string>
<string name="contrast_medium"></string>
<string name="contrast_high"></string>
</resources>

View file

@ -67,4 +67,12 @@
<string name="dark_mode_system">Follow System</string>
<string name="dark_mode_light">Light</string>
<string name="dark_mode_dark">Dark</string>
<string name="pref_contrast_level">Contrast Level</string>
<string name="pref_contrast_level_desc">Change the app\'s color contrast</string>
<string name="contrast_very_low">Very Low</string>
<string name="contrast_low">Low</string>
<string name="contrast_default">Default</string>
<string name="contrast_medium">Medium</string>
<string name="contrast_high">High</string>
<string name="tip_change_contrast_level">Change the contrast level</string>
</resources>