From 7ab13cd6f9ae153993124156ee723d267c570607 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:50:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(settings):=20=E6=94=AF=E6=8C=81=E6=B7=B1?= =?UTF-8?q?=E8=89=B2=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/super12138/todo/constants/Constants.kt | 3 + .../super12138/todo/constants/GlobalValues.kt | 4 + .../super12138/todo/logic/model/DarkMode.kt | 33 ++++++++ .../todo/ui/activities/MainActivity.kt | 24 +++++- .../todo/ui/navigation/TodoNavigation.kt | 1 + .../ui/pages/settings/SettingsAppearance.kt | 9 ++- .../components/darkmode/DarkModeItem.kt | 70 ++++++++++++++++ .../components/darkmode/DarkModePicker.kt | 80 +++++++++++++++++++ .../todo/ui/viewmodels/MainViewModel.kt | 10 +++ app/src/main/res/values-zh-rCN/strings.xml | 5 ++ app/src/main/res/values/strings.xml | 5 ++ 11 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModePicker.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 4ebdacb..835589c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt index 4520c8c..69524c5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt @@ -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 + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt new file mode 100644 index 0000000..7bd0cc7 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/DarkMode.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index aaf60fc..aab611d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -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() diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index 3a071d8..ce55816 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -90,6 +90,7 @@ fun TodoNavigation( composable(TodoScreen.SettingsAppearance.name) { SettingsAppearance( + viewModel = viewModel, onNavigateUp = { navController.navigateUp() } ) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index b53a235..13670eb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -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 }) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt new file mode 100644 index 0000000..9cea4c0 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModePicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModePicker.kt new file mode 100644 index 0000000..a78de22 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModePicker.kt @@ -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) + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index f57c493..05abed6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -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()) val selectedTodoIds = _selectedTodoIds.asStateFlow() @@ -108,4 +114,8 @@ class MainViewModel : ViewModel() { fun playConfetti() { showConfetti.value = true } + + fun setDarkMode(darkMode: DarkMode) { + appDarkMode = darkMode + } } \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5b14a1b..4856cb5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -61,4 +61,9 @@ 单色 高保真 内容 + 深色模式 + 开启或关闭应用深色模式 + 跟随系统 + 浅色模式 + 深色模式 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42d37c4..dfc3d93 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,4 +62,9 @@ Monochrome Fidelity Content + Dark Mode + Enable or disable the dark mode for this app + Follow System + Light + Dark \ No newline at end of file