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

View file

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

View file

@ -1,8 +1,10 @@
package cn.super12138.todo.ui.pages.settings.components package cn.super12138.todo.ui.pages.settings.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -40,6 +42,34 @@ fun RowSettingsItem(
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true, userScrollEnabled: Boolean = true,
content: LazyListScope.() -> Unit 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( Column(
modifier = modifier modifier = modifier
@ -70,16 +100,12 @@ fun RowSettingsItem(
Spacer(Modifier.size(8.dp)) Spacer(Modifier.size(8.dp))
LazyRow( /*Box(
modifier = Modifier.fillMaxWidth(), Modifier
state = state, .fillMaxWidth()
contentPadding = contentPadding, .clip(MaterialTheme.shapes.large)
reverseLayout = reverseLayout, .background(MaterialTheme.colorScheme.surfaceContainer)
horizontalArrangement = horizontalArrangement, ) {*/
verticalAlignment = verticalAlignment, content()
flingBehavior = flingBehavior,
userScrollEnabled = userScrollEnabled,
content = 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.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import cn.super12138.todo.constants.GlobalValues 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.PaletteStyle
import cn.super12138.todo.ui.theme.dynamicColorScheme import cn.super12138.todo.ui.theme.dynamicColorScheme
@Composable @Composable
fun PaletteItem( fun PaletteItem(
paletteStyle: PaletteStyle, paletteStyle: PaletteStyle,
contrastLevel: ContrastLevel,
selected: Boolean, selected: Boolean,
onSelect: () -> Unit, onSelect: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
@ -58,6 +60,7 @@ fun PaletteItem(
Color(0xFF0061A4) Color(0xFF0061A4)
}, },
isDark = isSystemInDarkTheme(), isDark = isSystemInDarkTheme(),
contrastLevel = contrastLevel.value.toDouble(),
style = paletteStyle style = paletteStyle
) )
) { ) {

View file

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

View file

@ -3,8 +3,10 @@ package cn.super12138.todo.ui.pages.settings.state
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableFloatState
import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -28,6 +30,12 @@ fun rememberPrefIntState(key: String, default: Int): MutableIntState {
return remember { PrefMutableIntState(context, key, default) } 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( private class PrefMutableBooleanState(
val context: Context, val context: Context,
val key: String, 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 * 来自https://github.com/hushenghao/AndroidEasterEggs/blob/main/basic/src/main/java/com/dede/android_eggs/util/Pref.kt
* @author hushenghao * @author hushenghao

View file

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

View file

@ -67,4 +67,12 @@
<string name="dark_mode_system">Follow System</string> <string name="dark_mode_system">Follow System</string>
<string name="dark_mode_light">Light</string> <string name="dark_mode_light">Light</string>
<string name="dark_mode_dark">Dark</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> </resources>