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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import cn.super12138.todo.logic.database.TodoDatabase
|
||||
import cn.super12138.todo.ui.pages.crash.CrashHandler
|
||||
|
||||
|
|
@ -8,6 +10,8 @@ class TodoApp : Application() {
|
|||
private val database by lazy { TodoDatabase.getDatabase(this) }
|
||||
|
||||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var context: Context
|
||||
lateinit var db: TodoDatabase
|
||||
}
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ class TodoApp : Application() {
|
|||
super.onCreate()
|
||||
|
||||
db = database
|
||||
context = applicationContext
|
||||
|
||||
val crashHandler = CrashHandler(applicationContext)
|
||||
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.settings.SettingsAbout
|
||||
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.theme.materialSharedAxisXIn
|
||||
import cn.super12138.todo.ui.theme.materialSharedAxisXOut
|
||||
|
|
@ -82,10 +83,17 @@ fun TodoNavigation(
|
|||
composable(TodoScreen.SettingsMain.name) {
|
||||
SettingsMain(
|
||||
onNavigateUp = { navController.navigateUp() },
|
||||
toAppearancePage = { navController.navigate(TodoScreen.SettingsAppearance.name) },
|
||||
toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(TodoScreen.SettingsAppearance.name) {
|
||||
SettingsAppearance(
|
||||
onNavigateUp = { navController.navigateUp() }
|
||||
)
|
||||
}
|
||||
|
||||
composable(TodoScreen.SettingsAbout.name) {
|
||||
SettingsAbout(
|
||||
onNavigateUp = { navController.navigateUp() },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ enum class TodoScreen {
|
|||
Main,
|
||||
TodoEditor,
|
||||
SettingsMain,
|
||||
SettingsAppearance,
|
||||
SettingsAbout,
|
||||
SettingsAboutLicence
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -29,8 +29,7 @@ fun SettingsAbout(
|
|||
title = stringResource(R.string.pref_about),
|
||||
onBack = onNavigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { innerPadding ->
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
|
|
@ -39,17 +38,17 @@ fun SettingsAbout(
|
|||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
SettingsItem(
|
||||
title = stringResource(R.string.pref_app_version),
|
||||
description = getAppVersion(context),
|
||||
enableClick = false
|
||||
)
|
||||
DefaultSettingsItem(
|
||||
SettingsItem(
|
||||
title = stringResource(R.string.pref_developer),
|
||||
description = stringResource(R.string.developer_name),
|
||||
enableClick = false
|
||||
)
|
||||
DefaultSettingsItem(
|
||||
SettingsItem(
|
||||
title = stringResource(R.string.pref_licence),
|
||||
description = stringResource(R.string.pref_licence_desc),
|
||||
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.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ColorLens
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
|
|
@ -14,12 +15,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
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)
|
||||
@Composable
|
||||
fun SettingsMain(
|
||||
onNavigateUp: () -> Unit,
|
||||
toAppearancePage: () -> Unit,
|
||||
toAboutPage: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
@ -35,7 +37,13 @@ fun SettingsMain(
|
|||
.padding(innerPadding)
|
||||
.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,
|
||||
title = stringResource(R.string.pref_about),
|
||||
description = stringResource(R.string.pref_about_desc),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
fun SettingsItem(
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
|
|
@ -28,7 +28,7 @@ fun DefaultSettingsItem(
|
|||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
SettingsItem(
|
||||
leadingIcon = leadingIcon,
|
||||
title = title,
|
||||
description = description,
|
||||
|
|
@ -40,7 +40,7 @@ fun DefaultSettingsItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
fun SettingsItem(
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
|
|
@ -49,7 +49,7 @@ fun DefaultSettingsItem(
|
|||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
SettingsItem(
|
||||
leadingIcon = {
|
||||
leadingIcon?.let {
|
||||
Icon(
|
||||
|
|
@ -70,7 +70,7 @@ fun DefaultSettingsItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
fun SettingsItem(
|
||||
leadingIcon: (@Composable () -> Unit)? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
|
|
@ -2,31 +2,40 @@ package cn.super12138.todo.ui.pages.settings.components
|
|||
|
||||
import androidx.compose.material3.Switch
|
||||
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.vector.ImageVector
|
||||
import cn.super12138.todo.ui.pages.settings.state.rememberPrefBooleanState
|
||||
|
||||
@Composable
|
||||
fun SwitchSettingsItem(
|
||||
key: String,
|
||||
default: Boolean,
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
var switchState by rememberPrefBooleanState(key, default)
|
||||
SettingsItem(
|
||||
leadingIcon = leadingIcon,
|
||||
title = title,
|
||||
description = description,
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier,
|
||||
checked = switchState,
|
||||
onCheckedChange = {
|
||||
switchState = it
|
||||
onCheckedChange(it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
onClick = {
|
||||
switchState = !switchState
|
||||
onCheckedChange(switchState)
|
||||
},
|
||||
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
|
||||
|
||||
enum class PaletteStyle {
|
||||
TonalSpot,
|
||||
Neutral,
|
||||
Vibrant,
|
||||
Expressive,
|
||||
Rainbow,
|
||||
FruitSalad,
|
||||
Monochrome,
|
||||
Fidelity,
|
||||
Content,
|
||||
import android.content.Context
|
||||
import cn.super12138.todo.R
|
||||
|
||||
enum class PaletteStyle(val id: Int) {
|
||||
TonalSpot(1),
|
||||
Neutral(2),
|
||||
Vibrant(3),
|
||||
Expressive(4),
|
||||
Rainbow(5),
|
||||
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.material3.MaterialTheme
|
||||
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.res.colorResource
|
||||
import cn.super12138.todo.constants.GlobalValues
|
||||
|
||||
var isDynamicColorEnable by mutableStateOf(GlobalValues.dynamicColor)
|
||||
var appPaletteStyle by mutableStateOf(PaletteStyle.fromId(GlobalValues.paletteStyle))
|
||||
|
||||
@Composable
|
||||
fun ToDoTheme(
|
||||
color: Color? = null,
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
style: PaletteStyle = PaletteStyle.TonalSpot,
|
||||
style: PaletteStyle = appPaletteStyle,
|
||||
contrastLevel: Double = 0.0,
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
dynamicColor: Boolean = isDynamicColorEnable,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
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
|
||||
|
||||
class MainViewModel : ViewModel() {
|
||||
// 待办
|
||||
val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
||||
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_urgent">紧急</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>
|
||||
|
|
@ -47,4 +47,18 @@
|
|||
<string name="priority_important">Important</string>
|
||||
<string name="priority_urgent">Urgent</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>
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
# Android X
|
||||
coreKtx = "1.15.0"
|
||||
splashScreen = "1.2.0-alpha02"
|
||||
# security = "1.1.0-alpha06"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
# security = "1.1.0-alpha06"
|
||||
# Compose
|
||||
activityCompose = "1.9.3"
|
||||
composeBom = "2024.12.01"
|
||||
|
|
|
|||
Loading…
Reference in a new issue