feat(settings): 支持深色模式

This commit is contained in:
Super12138 2025-02-05 13:50:36 +08:00
parent 72b75e2353
commit 7ab13cd6f9
11 changed files with 239 additions and 5 deletions

View file

@ -8,4 +8,7 @@ object Constants {
const val PREF_PALETTE_STYLE = "palette_style"
const val PREF_PALETTE_STYLE_DEFAULT = 1 // TonalSpot
const val PREF_DARK_MODE = "dark_mode"
const val PREF_DARK_MODE_DEFAULT = 1 // Follow System
}

View file

@ -11,4 +11,8 @@ object GlobalValues {
Constants.PREF_PALETTE_STYLE,
Constants.PREF_PALETTE_STYLE_DEFAULT
)
var darkMode: Int by SPDelegates(
Constants.PREF_DARK_MODE,
Constants.PREF_DARK_MODE_DEFAULT
)
}

View file

@ -0,0 +1,33 @@
package cn.super12138.todo.logic.model
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DarkMode
import androidx.compose.material.icons.outlined.LightMode
import androidx.compose.material.icons.outlined.SettingsSuggest
import androidx.compose.ui.graphics.vector.ImageVector
import cn.super12138.todo.R
enum class DarkMode(
val id: Int,
val icon: ImageVector
) {
FollowSystem(-1, Icons.Outlined.SettingsSuggest),
Light(1, Icons.Outlined.LightMode),
Dark(2, Icons.Outlined.DarkMode);
fun getDisplayName(context: Context): String {
val resId = when (this) {
FollowSystem -> R.string.dark_mode_system
Light -> R.string.dark_mode_light
Dark -> R.string.dark_mode_dark
}
return context.getString(resId)
}
companion object {
fun fromId(id: Int): DarkMode {
return DarkMode.entries.find { it.id == id } ?: FollowSystem
}
}
}

View file

@ -4,11 +4,17 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import cn.super12138.todo.logic.model.DarkMode.Dark
import cn.super12138.todo.logic.model.DarkMode.FollowSystem
import cn.super12138.todo.logic.model.DarkMode.Light
import cn.super12138.todo.ui.components.Konfetti
import cn.super12138.todo.ui.navigation.TodoNavigation
import cn.super12138.todo.ui.theme.ToDoTheme
@ -19,10 +25,22 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
ToDoTheme {
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 {
isAppearanceLightStatusBars = !darkTheme
isAppearanceLightNavigationBars = !darkTheme
}
}
ToDoTheme(darkTheme = darkTheme) {
Surface(color = MaterialTheme.colorScheme.background) {
val mainViewModel: MainViewModel = viewModel()
val showConfetti = mainViewModel.showConfetti
TodoNavigation(
viewModel = mainViewModel,
modifier = Modifier.fillMaxSize()

View file

@ -90,6 +90,7 @@ fun TodoNavigation(
composable(TodoScreen.SettingsAppearance.name) {
SettingsAppearance(
viewModel = viewModel,
onNavigateUp = { navController.navigateUp() }
)
}

View file

@ -17,13 +17,16 @@ 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.darkmode.DarkModePicker
import cn.super12138.todo.ui.pages.settings.components.palette.PalettePicker
import cn.super12138.todo.ui.theme.appPaletteStyle
import cn.super12138.todo.ui.theme.isDynamicColorEnable
import cn.super12138.todo.ui.viewmodels.MainViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsAppearance(
viewModel: MainViewModel,
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
@ -40,6 +43,10 @@ fun SettingsAppearance(
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
DarkModePicker(onDarkModeChange = { viewModel.setDarkMode(it) })
PalettePicker(onPaletteChange = { appPaletteStyle = it })
SwitchSettingsItem(
key = Constants.PREF_DYNAMIC_COLOR,
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT,
@ -48,8 +55,6 @@ fun SettingsAppearance(
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
onCheckedChange = { isDynamicColorEnable = it },
)
PalettePicker(onPaletteChange = { appPaletteStyle = it })
}
}
}

View file

@ -0,0 +1,70 @@
package cn.super12138.todo.ui.pages.settings.components.darkmode
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@Composable
fun DarkModeItem(
icon: ImageVector,
contentDescription: String,
contentColor: Color,
containerColor: Color,
selected: Boolean,
onSelect: () -> Unit,
modifier: Modifier = Modifier
) {
val borderWidth by animateDpAsState(if (selected) 3.dp else (-1).dp)
Column(
modifier = modifier
.clip(MaterialTheme.shapes.large)
.clickable { onSelect() }
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(90.dp)
.clip(MaterialTheme.shapes.large)
.background(containerColor)
.border(
width = borderWidth,
color = MaterialTheme.colorScheme.primary,
shape = MaterialTheme.shapes.large
),
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = contentColor,
modifier = Modifier
.size(30.dp)
.align(Alignment.Center)
)
}
Spacer(Modifier.size(8.dp))
Text(
text = contentDescription,
style = MaterialTheme.typography.bodyMedium
)
}
}

View file

@ -0,0 +1,80 @@
package cn.super12138.todo.ui.pages.settings.components.darkmode
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
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.DarkMode
import cn.super12138.todo.ui.pages.settings.components.RowSettingsItem
import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState
@Composable
fun DarkModePicker(
onDarkModeChange: (darkMode: DarkMode) -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val isInDarkTheme = isSystemInDarkTheme()
var darkModeState by rememberPrefIntState(
Constants.PREF_DARK_MODE,
Constants.PREF_DARK_MODE_DEFAULT
)
RowSettingsItem(
title = stringResource(R.string.pref_dark_mode),
description = stringResource(R.string.pref_dark_mode_desc),
horizontalArrangement = Arrangement.spacedBy(5.dp)
) {
item {
DarkModeItem(
icon = DarkMode.FollowSystem.icon,
contentDescription = DarkMode.FollowSystem.getDisplayName(context),
contentColor = if (isInDarkTheme) Color.White else Color.Black,
containerColor = if (isInDarkTheme) Color.Black else Color.White,
selected = DarkMode.fromId(darkModeState) == DarkMode.FollowSystem,
onSelect = {
darkModeState = DarkMode.FollowSystem.id
onDarkModeChange(DarkMode.FollowSystem)
}
)
}
item {
DarkModeItem(
icon = DarkMode.Light.icon,
contentDescription = DarkMode.Light.getDisplayName(context),
contentColor = Color.Black,
containerColor = Color.White,
selected = DarkMode.fromId(darkModeState) == DarkMode.Light,
onSelect = {
darkModeState = DarkMode.Light.id
onDarkModeChange(DarkMode.Light)
}
)
}
item {
DarkModeItem(
icon = DarkMode.Dark.icon,
contentDescription = DarkMode.Dark.getDisplayName(context),
contentColor = Color.White,
containerColor = Color.Black,
selected = DarkMode.fromId(darkModeState) == DarkMode.Dark,
onSelect = {
darkModeState = DarkMode.Dark.id
onDarkModeChange(DarkMode.Dark)
}
)
}
}
}

View file

@ -5,8 +5,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
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.DarkMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -21,6 +23,10 @@ class MainViewModel : ViewModel() {
private set
val showConfetti = mutableStateOf(false)
// 深色模式
var appDarkMode by mutableStateOf(DarkMode.fromId(GlobalValues.darkMode))
private set
// 多选逻辑参考https://github.com/X1nto/Mauth
private val _selectedTodoIds = MutableStateFlow(listOf<Int>())
val selectedTodoIds = _selectedTodoIds.asStateFlow()
@ -108,4 +114,8 @@ class MainViewModel : ViewModel() {
fun playConfetti() {
showConfetti.value = true
}
fun setDarkMode(darkMode: DarkMode) {
appDarkMode = darkMode
}
}

View file

@ -61,4 +61,9 @@
<string name="palette_monochrome">单色</string>
<string name="palette_fidelity">高保真</string>
<string name="palette_content">内容</string>
<string name="pref_dark_mode">深色模式</string>
<string name="pref_dark_mode_desc">开启或关闭应用深色模式</string>
<string name="dark_mode_system">跟随系统</string>
<string name="dark_mode_light">浅色模式</string>
<string name="dark_mode_dark">深色模式</string>
</resources>

View file

@ -62,4 +62,9 @@
<string name="palette_monochrome">Monochrome</string>
<string name="palette_fidelity">Fidelity</string>
<string name="palette_content">Content</string>
<string name="pref_dark_mode">Dark Mode</string>
<string name="pref_dark_mode_desc">Enable or disable the dark mode for this app</string>
<string name="dark_mode_system">Follow System</string>
<string name="dark_mode_light">Light</string>
<string name="dark_mode_dark">Dark</string>
</resources>