feat(settings): 添加外观与个性化设置
里面有动态配色和主题样式两个设置的实现 后续还有待优化 SharedPreferences 的读写方式
This commit is contained in:
parent
e729125111
commit
32e22929ed
21 changed files with 522 additions and 33 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
package cn.super12138.todo
|
package cn.super12138.todo
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import cn.super12138.todo.logic.database.TodoDatabase
|
import cn.super12138.todo.logic.database.TodoDatabase
|
||||||
import cn.super12138.todo.ui.pages.crash.CrashHandler
|
import cn.super12138.todo.ui.pages.crash.CrashHandler
|
||||||
|
|
||||||
|
|
@ -8,6 +10,8 @@ class TodoApp : Application() {
|
||||||
private val database by lazy { TodoDatabase.getDatabase(this) }
|
private val database by lazy { TodoDatabase.getDatabase(this) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
lateinit var context: Context
|
||||||
lateinit var db: TodoDatabase
|
lateinit var db: TodoDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +19,7 @@ class TodoApp : Application() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
db = database
|
db = database
|
||||||
|
context = applicationContext
|
||||||
|
|
||||||
val crashHandler = CrashHandler(applicationContext)
|
val crashHandler = CrashHandler(applicationContext)
|
||||||
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
|
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.super12138.todo.constants
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val SP_NAME = "cn.super12138.todo_preferences"
|
||||||
|
|
||||||
|
const val PREF_DYNAMIC_COLOR = "dynamic_color"
|
||||||
|
const val PREF_DYNAMIC_COLOR_DEFAULT = true
|
||||||
|
|
||||||
|
const val PREF_PALETTE_STYLE = "palette_style"
|
||||||
|
const val PREF_PALETTE_STYLE_DEFAULT = 1 // TonalSpot
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package cn.super12138.todo.constants
|
||||||
|
|
||||||
|
import cn.super12138.todo.utils.SPDelegates
|
||||||
|
|
||||||
|
object GlobalValues {
|
||||||
|
var dynamicColor: Boolean by SPDelegates(
|
||||||
|
Constants.PREF_DYNAMIC_COLOR,
|
||||||
|
Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
||||||
|
)
|
||||||
|
var paletteStyle: Int by SPDelegates(
|
||||||
|
Constants.PREF_PALETTE_STYLE,
|
||||||
|
Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import cn.super12138.todo.ui.pages.editor.TodoEditorPage
|
||||||
import cn.super12138.todo.ui.pages.main.MainPage
|
import cn.super12138.todo.ui.pages.main.MainPage
|
||||||
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
||||||
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
||||||
|
import cn.super12138.todo.ui.pages.settings.SettingsAppearance
|
||||||
import cn.super12138.todo.ui.pages.settings.SettingsMain
|
import cn.super12138.todo.ui.pages.settings.SettingsMain
|
||||||
import cn.super12138.todo.ui.theme.materialSharedAxisXIn
|
import cn.super12138.todo.ui.theme.materialSharedAxisXIn
|
||||||
import cn.super12138.todo.ui.theme.materialSharedAxisXOut
|
import cn.super12138.todo.ui.theme.materialSharedAxisXOut
|
||||||
|
|
@ -82,10 +83,17 @@ fun TodoNavigation(
|
||||||
composable(TodoScreen.SettingsMain.name) {
|
composable(TodoScreen.SettingsMain.name) {
|
||||||
SettingsMain(
|
SettingsMain(
|
||||||
onNavigateUp = { navController.navigateUp() },
|
onNavigateUp = { navController.navigateUp() },
|
||||||
|
toAppearancePage = { navController.navigate(TodoScreen.SettingsAppearance.name) },
|
||||||
toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }
|
toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(TodoScreen.SettingsAppearance.name) {
|
||||||
|
SettingsAppearance(
|
||||||
|
onNavigateUp = { navController.navigateUp() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable(TodoScreen.SettingsAbout.name) {
|
composable(TodoScreen.SettingsAbout.name) {
|
||||||
SettingsAbout(
|
SettingsAbout(
|
||||||
onNavigateUp = { navController.navigateUp() },
|
onNavigateUp = { navController.navigateUp() },
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ enum class TodoScreen {
|
||||||
Main,
|
Main,
|
||||||
TodoEditor,
|
TodoEditor,
|
||||||
SettingsMain,
|
SettingsMain,
|
||||||
|
SettingsAppearance,
|
||||||
SettingsAbout,
|
SettingsAbout,
|
||||||
SettingsAboutLicence
|
SettingsAboutLicence
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.settings.components.DefaultSettingsItem
|
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||||
import cn.super12138.todo.utils.getAppVersion
|
import cn.super12138.todo.utils.getAppVersion
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -29,8 +29,7 @@ fun SettingsAbout(
|
||||||
title = stringResource(R.string.pref_about),
|
title = stringResource(R.string.pref_about),
|
||||||
onBack = onNavigateUp,
|
onBack = onNavigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
modifier = modifier
|
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -39,17 +38,17 @@ fun SettingsAbout(
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
title = stringResource(R.string.pref_app_version),
|
title = stringResource(R.string.pref_app_version),
|
||||||
description = getAppVersion(context),
|
description = getAppVersion(context),
|
||||||
enableClick = false
|
enableClick = false
|
||||||
)
|
)
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
title = stringResource(R.string.pref_developer),
|
title = stringResource(R.string.pref_developer),
|
||||||
description = stringResource(R.string.developer_name),
|
description = stringResource(R.string.developer_name),
|
||||||
enableClick = false
|
enableClick = false
|
||||||
)
|
)
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
title = stringResource(R.string.pref_licence),
|
title = stringResource(R.string.pref_licence),
|
||||||
description = stringResource(R.string.pref_licence_desc),
|
description = stringResource(R.string.pref_licence_desc),
|
||||||
onClick = toLicencePage
|
onClick = toLicencePage
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package cn.super12138.todo.ui.pages.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ColorLens
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
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.palette.PalettePicker
|
||||||
|
import cn.super12138.todo.ui.theme.appPaletteStyle
|
||||||
|
import cn.super12138.todo.ui.theme.isDynamicColorEnable
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsAppearance(
|
||||||
|
onNavigateUp: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
LargeTopAppBarScaffold(
|
||||||
|
title = stringResource(R.string.pref_appearance),
|
||||||
|
onBack = onNavigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
) { innerPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
SwitchSettingsItem(
|
||||||
|
key = Constants.PREF_DYNAMIC_COLOR,
|
||||||
|
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT,
|
||||||
|
leadingIcon = Icons.Outlined.ColorLens,
|
||||||
|
title = stringResource(R.string.pref_appearance_dynamic_color),
|
||||||
|
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
|
||||||
|
onCheckedChange = { isDynamicColorEnable = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
PalettePicker(onPaletteChange = { appPaletteStyle = it })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ColorLens
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
|
@ -14,12 +15,13 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.settings.components.DefaultSettingsItem
|
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsMain(
|
fun SettingsMain(
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
|
toAppearancePage: () -> Unit,
|
||||||
toAboutPage: () -> Unit,
|
toAboutPage: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
@ -35,7 +37,13 @@ fun SettingsMain(
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
|
leadingIcon = Icons.Outlined.ColorLens,
|
||||||
|
title = stringResource(R.string.pref_appearance),
|
||||||
|
description = stringResource(R.string.pref_appearance_desc),
|
||||||
|
onClick = toAppearancePage
|
||||||
|
)
|
||||||
|
SettingsItem(
|
||||||
leadingIcon = Icons.Outlined.Info,
|
leadingIcon = Icons.Outlined.Info,
|
||||||
title = stringResource(R.string.pref_about),
|
title = stringResource(R.string.pref_about),
|
||||||
description = stringResource(R.string.pref_about_desc),
|
description = stringResource(R.string.pref_about_desc),
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultSettingsItem(
|
fun SettingsItem(
|
||||||
leadingIcon: ImageVector? = null,
|
leadingIcon: ImageVector? = null,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
|
|
@ -28,7 +28,7 @@ fun DefaultSettingsItem(
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
leadingIcon = leadingIcon,
|
leadingIcon = leadingIcon,
|
||||||
title = title,
|
title = title,
|
||||||
description = description,
|
description = description,
|
||||||
|
|
@ -40,7 +40,7 @@ fun DefaultSettingsItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultSettingsItem(
|
fun SettingsItem(
|
||||||
leadingIcon: ImageVector? = null,
|
leadingIcon: ImageVector? = null,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
|
|
@ -49,7 +49,7 @@ fun DefaultSettingsItem(
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
DefaultSettingsItem(
|
SettingsItem(
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
leadingIcon?.let {
|
leadingIcon?.let {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -70,7 +70,7 @@ fun DefaultSettingsItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultSettingsItem(
|
fun SettingsItem(
|
||||||
leadingIcon: (@Composable () -> Unit)? = null,
|
leadingIcon: (@Composable () -> Unit)? = null,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
|
|
@ -2,31 +2,40 @@ package cn.super12138.todo.ui.pages.settings.components
|
||||||
|
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import cn.super12138.todo.ui.pages.settings.state.rememberPrefBooleanState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchSettingsItem(
|
fun SwitchSettingsItem(
|
||||||
|
key: String,
|
||||||
|
default: Boolean,
|
||||||
leadingIcon: ImageVector? = null,
|
leadingIcon: ImageVector? = null,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
checked: Boolean,
|
|
||||||
onCheckedChange: (Boolean) -> Unit,
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
onClick: () -> Unit = {},
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
DefaultSettingsItem(
|
var switchState by rememberPrefBooleanState(key, default)
|
||||||
|
SettingsItem(
|
||||||
leadingIcon = leadingIcon,
|
leadingIcon = leadingIcon,
|
||||||
title = title,
|
title = title,
|
||||||
description = description,
|
description = description,
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = switchState,
|
||||||
onCheckedChange = onCheckedChange,
|
onCheckedChange = {
|
||||||
modifier = modifier,
|
switchState = it
|
||||||
|
onCheckedChange(it)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = {
|
||||||
|
switchState = !switchState
|
||||||
|
onCheckedChange(switchState)
|
||||||
|
},
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package cn.super12138.todo.ui.pages.settings.components.palette
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
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.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
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.platform.LocalContext
|
||||||
|
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.ui.theme.PaletteStyle
|
||||||
|
import cn.super12138.todo.ui.theme.dynamicColorScheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PaletteItem(
|
||||||
|
paletteStyle: PaletteStyle,
|
||||||
|
selected: Boolean,
|
||||||
|
onSelect: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
// 单个主题最外层布局
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(90.dp)
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.clickable {
|
||||||
|
onSelect()
|
||||||
|
}
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
// 为不同主题样式设置不同色板
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = dynamicColorScheme(
|
||||||
|
keyColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && GlobalValues.dynamicColor) {
|
||||||
|
colorResource(id = android.R.color.system_accent1_500)
|
||||||
|
} else {
|
||||||
|
Color(0xFFFF0000)
|
||||||
|
},
|
||||||
|
isDark = isSystemInDarkTheme(),
|
||||||
|
style = paletteStyle
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val borderWidth by animateDpAsState(if (selected) 3.dp else (-1).dp)
|
||||||
|
// 颜色预览区域
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(70.dp)
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.border(
|
||||||
|
width = borderWidth,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = MaterialTheme.shapes.large
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
|
) {
|
||||||
|
listOf(
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
MaterialTheme.colorScheme.secondary,
|
||||||
|
MaterialTheme.colorScheme.tertiary,
|
||||||
|
MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
).fastForEach {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(24.dp)
|
||||||
|
.background(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = paletteStyle.getDisplayName(context),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cn.super12138.todo.ui.pages.settings.components.palette
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import cn.super12138.todo.R
|
||||||
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState
|
||||||
|
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PalettePicker(
|
||||||
|
onPaletteChange: (paletteStyle: PaletteStyle) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var paletteState by rememberPrefIntState(
|
||||||
|
Constants.PREF_PALETTE_STYLE,
|
||||||
|
Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.pref_palette_style),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = 20.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
val paletteOptions = remember {
|
||||||
|
PaletteStyle.entries.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
items(items = paletteOptions, key = { it.name }) { paletteStyle ->
|
||||||
|
PaletteItem(
|
||||||
|
paletteStyle = paletteStyle,
|
||||||
|
selected = PaletteStyle.fromId(paletteState) == paletteStyle,
|
||||||
|
onSelect = {
|
||||||
|
paletteState = paletteStyle.id
|
||||||
|
onPaletteChange(paletteStyle)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
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.MutableIntState
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来自:https://github.com/hushenghao/AndroidEasterEggs/blob/main/app/src/main/java/com/dede/android_eggs/views/settings/compose/basic/PrefMutableState.kt
|
||||||
|
* 在命名上有改动
|
||||||
|
* @author hushenghao
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberPrefBooleanState(key: String, default: Boolean): MutableState<Boolean> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
return remember { PrefMutableBooleanState(context, key, default) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberPrefIntState(key: String, default: Int): MutableIntState {
|
||||||
|
val context = LocalContext.current
|
||||||
|
return remember { PrefMutableIntState(context, key, default) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PrefMutableBooleanState(
|
||||||
|
val context: Context,
|
||||||
|
val key: String,
|
||||||
|
default: Boolean,
|
||||||
|
) : MutableState<Boolean> {
|
||||||
|
private val delegate = mutableStateOf(context.pref.getBoolean(key, default))
|
||||||
|
|
||||||
|
override var value: Boolean
|
||||||
|
get() = delegate.value
|
||||||
|
set(value) {
|
||||||
|
delegate.value = value
|
||||||
|
context.pref.edit().putBoolean(key, value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component1(): Boolean {
|
||||||
|
return delegate.component1()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component2(): (Boolean) -> Unit {
|
||||||
|
return delegate.component2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PrefMutableIntState(
|
||||||
|
val context: Context,
|
||||||
|
val key: String,
|
||||||
|
default: Int,
|
||||||
|
) : MutableIntState {
|
||||||
|
private val delegate = mutableIntStateOf(context.pref.getInt(key, default))
|
||||||
|
|
||||||
|
override var intValue: Int
|
||||||
|
get() = delegate.intValue
|
||||||
|
set(value) {
|
||||||
|
delegate.intValue = value
|
||||||
|
context.pref.edit().putInt(key, value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component1(): Int {
|
||||||
|
return delegate.component1()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component2(): (Int) -> Unit {
|
||||||
|
return delegate.component2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来自:https://github.com/hushenghao/AndroidEasterEggs/blob/main/basic/src/main/java/com/dede/android_eggs/util/Pref.kt
|
||||||
|
* @author hushenghao
|
||||||
|
*/
|
||||||
|
val Context.pref: SharedPreferences
|
||||||
|
get() {
|
||||||
|
return applicationContext.getSharedPreferences(
|
||||||
|
applicationContext.packageName + "_preferences",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,37 @@
|
||||||
package cn.super12138.todo.ui.theme
|
package cn.super12138.todo.ui.theme
|
||||||
|
|
||||||
enum class PaletteStyle {
|
import android.content.Context
|
||||||
TonalSpot,
|
import cn.super12138.todo.R
|
||||||
Neutral,
|
|
||||||
Vibrant,
|
enum class PaletteStyle(val id: Int) {
|
||||||
Expressive,
|
TonalSpot(1),
|
||||||
Rainbow,
|
Neutral(2),
|
||||||
FruitSalad,
|
Vibrant(3),
|
||||||
Monochrome,
|
Expressive(4),
|
||||||
Fidelity,
|
Rainbow(5),
|
||||||
Content,
|
FruitSalad(6),
|
||||||
|
Monochrome(7),
|
||||||
|
Fidelity(8),
|
||||||
|
Content(9);
|
||||||
|
|
||||||
|
fun getDisplayName(context: Context): String {
|
||||||
|
val resId = when (this) {
|
||||||
|
TonalSpot -> R.string.palette_tonal_spot
|
||||||
|
Neutral -> R.string.palette_neutral
|
||||||
|
Vibrant -> R.string.palette_vibrant
|
||||||
|
Expressive -> R.string.palette_expressive
|
||||||
|
Rainbow -> R.string.palette_rainbow
|
||||||
|
FruitSalad -> R.string.palette_fruit_salad
|
||||||
|
Monochrome -> R.string.palette_monochrome
|
||||||
|
Fidelity -> R.string.palette_fidelity
|
||||||
|
Content -> R.string.palette_content
|
||||||
|
}
|
||||||
|
return context.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromId(id: Int): PaletteStyle {
|
||||||
|
return PaletteStyle.entries.find { it.id == id } ?: TonalSpot
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,17 +4,24 @@ import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
|
import cn.super12138.todo.constants.GlobalValues
|
||||||
|
|
||||||
|
var isDynamicColorEnable by mutableStateOf(GlobalValues.dynamicColor)
|
||||||
|
var appPaletteStyle by mutableStateOf(PaletteStyle.fromId(GlobalValues.paletteStyle))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ToDoTheme(
|
fun ToDoTheme(
|
||||||
color: Color? = null,
|
color: Color? = null,
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
style: PaletteStyle = PaletteStyle.TonalSpot,
|
style: PaletteStyle = appPaletteStyle,
|
||||||
contrastLevel: Double = 0.0,
|
contrastLevel: Double = 0.0,
|
||||||
// Dynamic color is available on Android 12+
|
// Dynamic color is available on Android 12+
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = isDynamicColorEnable,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val baseColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && dynamicColor) {
|
val baseColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && dynamicColor) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
|
// 待办
|
||||||
val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||||
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
|
||||||
14
app/src/main/kotlin/cn/super12138/todo/utils/SPDelegates.kt
Normal file
14
app/src/main/kotlin/cn/super12138/todo/utils/SPDelegates.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package cn.super12138.todo.utils
|
||||||
|
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class SPDelegates<T>(private val key: String, private val default: T) : ReadWriteProperty<Any?, T> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
return SPUtils.getValue(key, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
|
SPUtils.putValue(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/src/main/kotlin/cn/super12138/todo/utils/SPUtils.kt
Normal file
37
app/src/main/kotlin/cn/super12138/todo/utils/SPUtils.kt
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package cn.super12138.todo.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import cn.super12138.todo.TodoApp
|
||||||
|
import cn.super12138.todo.constants.Constants
|
||||||
|
|
||||||
|
object SPUtils {
|
||||||
|
private val sp: SharedPreferences by lazy {
|
||||||
|
TodoApp.context.getSharedPreferences(Constants.SP_NAME, Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> getValue(name: String, default: T): T = with(sp) {
|
||||||
|
val res: Any = when (default) {
|
||||||
|
is Long -> getLong(name, default)
|
||||||
|
is String -> getString(name, default).orEmpty()
|
||||||
|
is Int -> getInt(name, default)
|
||||||
|
is Boolean -> getBoolean(name, default)
|
||||||
|
is Float -> getFloat(name, default)
|
||||||
|
else -> throw java.lang.IllegalArgumentException()
|
||||||
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
res as T
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> putValue(name: String, value: T) = with(sp.edit()) {
|
||||||
|
when (value) {
|
||||||
|
is Long -> putLong(name, value)
|
||||||
|
is String -> putString(name, value)
|
||||||
|
is Int -> putInt(name, value)
|
||||||
|
is Boolean -> putBoolean(name, value)
|
||||||
|
is Float -> putFloat(name, value)
|
||||||
|
else -> throw IllegalArgumentException("This type can't be saved into Preferences")
|
||||||
|
}.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -46,4 +46,18 @@
|
||||||
<string name="priority_important">重要</string>
|
<string name="priority_important">重要</string>
|
||||||
<string name="priority_urgent">紧急</string>
|
<string name="priority_urgent">紧急</string>
|
||||||
<string name="label_priority">优先级</string>
|
<string name="label_priority">优先级</string>
|
||||||
|
<string name="pref_appearance">外观和个性化</string>
|
||||||
|
<string name="pref_appearance_desc">主题、色彩样式</string>
|
||||||
|
<string name="pref_appearance_dynamic_color">动态配色</string>
|
||||||
|
<string name="pref_appearance_dynamic_color_desc">使用系统壁纸的主题颜色,仅在 Android 12+ 上生效</string>
|
||||||
|
<string name="pref_palette_style">主题样式</string>
|
||||||
|
<string name="palette_tonal_spot">色调点</string>
|
||||||
|
<string name="palette_neutral">中性</string>
|
||||||
|
<string name="palette_vibrant">鲜艳</string>
|
||||||
|
<string name="palette_expressive">表现力</string>
|
||||||
|
<string name="palette_rainbow">彩虹</string>
|
||||||
|
<string name="palette_fruit_salad">水果沙拉</string>
|
||||||
|
<string name="palette_monochrome">单色</string>
|
||||||
|
<string name="palette_fidelity">高保真</string>
|
||||||
|
<string name="palette_content">内容</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -47,4 +47,18 @@
|
||||||
<string name="priority_important">Important</string>
|
<string name="priority_important">Important</string>
|
||||||
<string name="priority_urgent">Urgent</string>
|
<string name="priority_urgent">Urgent</string>
|
||||||
<string name="label_priority">Priority</string>
|
<string name="label_priority">Priority</string>
|
||||||
|
<string name="pref_appearance">Appearance</string>
|
||||||
|
<string name="pref_appearance_desc">Theme, color style</string>
|
||||||
|
<string name="pref_appearance_dynamic_color">Dynamic Color</string>
|
||||||
|
<string name="pref_appearance_dynamic_color_desc">Use the system wallpaper\'s theme color, available on Android 12+</string>
|
||||||
|
<string name="pref_palette_style">Palette Style</string>
|
||||||
|
<string name="palette_tonal_spot">Tonal Spot</string>
|
||||||
|
<string name="palette_neutral">Neutral</string>
|
||||||
|
<string name="palette_vibrant">Vibrant</string>
|
||||||
|
<string name="palette_expressive">Expressive</string>
|
||||||
|
<string name="palette_rainbow">Rainbow</string>
|
||||||
|
<string name="palette_fruit_salad">Fruit Salad</string>
|
||||||
|
<string name="palette_monochrome">Monochrome</string>
|
||||||
|
<string name="palette_fidelity">Fidelity</string>
|
||||||
|
<string name="palette_content">Content</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
# Android X
|
# Android X
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.15.0"
|
||||||
splashScreen = "1.2.0-alpha02"
|
splashScreen = "1.2.0-alpha02"
|
||||||
# security = "1.1.0-alpha06"
|
|
||||||
lifecycleRuntimeKtx = "2.8.7"
|
lifecycleRuntimeKtx = "2.8.7"
|
||||||
|
# security = "1.1.0-alpha06"
|
||||||
# Compose
|
# Compose
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.9.3"
|
||||||
composeBom = "2024.12.01"
|
composeBom = "2024.12.01"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue