refactor: 用 Preference DataStore 代替 Preference
This commit is contained in:
parent
65258660cd
commit
d7eec36fc8
25 changed files with 344 additions and 390 deletions
|
|
@ -85,6 +85,7 @@ dependencies {
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.compose.runtime.livedata)
|
implementation(libs.androidx.compose.runtime.livedata)
|
||||||
|
implementation(libs.androidx.datastore.preferences)
|
||||||
// Compose
|
// Compose
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package cn.super12138.todo.constants
|
|
||||||
|
|
||||||
import cn.super12138.todo.utils.SPDelegates
|
|
||||||
|
|
||||||
object GlobalValues {
|
|
||||||
var dynamicColor: Boolean by SPDelegates(
|
|
||||||
key = Constants.PREF_DYNAMIC_COLOR,
|
|
||||||
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
|
||||||
)
|
|
||||||
var paletteStyle: Int by SPDelegates(
|
|
||||||
key = Constants.PREF_PALETTE_STYLE,
|
|
||||||
default = Constants.PREF_PALETTE_STYLE_DEFAULT
|
|
||||||
)
|
|
||||||
var darkMode: Int by SPDelegates(
|
|
||||||
key = Constants.PREF_DARK_MODE,
|
|
||||||
default = Constants.PREF_DARK_MODE_DEFAULT
|
|
||||||
)
|
|
||||||
var contrastLevel: Float by SPDelegates(
|
|
||||||
key = Constants.PREF_CONTRAST_LEVEL,
|
|
||||||
default = Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
|
||||||
)
|
|
||||||
var showCompleted: Boolean by SPDelegates(
|
|
||||||
key = Constants.PREF_SHOW_COMPLETED,
|
|
||||||
default = Constants.PREF_SHOW_COMPLETED_DEFAULT
|
|
||||||
)
|
|
||||||
var sortingMethod: Int by SPDelegates(
|
|
||||||
key = Constants.PREF_SORTING_METHOD,
|
|
||||||
default = Constants.PREF_SORTING_METHOD_DEFAULT
|
|
||||||
)
|
|
||||||
var secureMode: Boolean by SPDelegates(
|
|
||||||
key = Constants.PREF_SECURE_MODE,
|
|
||||||
default = Constants.PREF_SECURE_MODE_DEFAULT
|
|
||||||
)
|
|
||||||
var hapticFeedback: Boolean by SPDelegates(
|
|
||||||
key = Constants.PREF_HAPTIC_FEEDBACK,
|
|
||||||
default = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package cn.super12138.todo.logic.datastore
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.floatPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import cn.super12138.todo.TodoApp
|
||||||
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
object DataStoreManager {
|
||||||
|
private val Context.dataStore by preferencesDataStore(
|
||||||
|
name = Constants.SP_NAME,
|
||||||
|
produceMigrations = { context ->
|
||||||
|
listOf(
|
||||||
|
SharedPreferencesMigration(
|
||||||
|
context = context,
|
||||||
|
sharedPreferencesName = Constants.SP_NAME,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val dataStore = TodoApp.context.dataStore
|
||||||
|
|
||||||
|
// Keys
|
||||||
|
// 外观与个性化
|
||||||
|
private val DYNAMIC_COLOR = booleanPreferencesKey(Constants.PREF_DYNAMIC_COLOR)
|
||||||
|
private val PALETTE_STYLE = intPreferencesKey(Constants.PREF_PALETTE_STYLE)
|
||||||
|
private val DARK_MODE = intPreferencesKey(Constants.PREF_DARK_MODE)
|
||||||
|
private val CONTRAST_LEVEL = floatPreferencesKey(Constants.PREF_CONTRAST_LEVEL)
|
||||||
|
|
||||||
|
// 界面与交互
|
||||||
|
private val SHOW_COMPLETED = booleanPreferencesKey(Constants.PREF_SHOW_COMPLETED)
|
||||||
|
private val SORTING_METHOD = intPreferencesKey(Constants.PREF_SORTING_METHOD)
|
||||||
|
private val SECURE_MODE = booleanPreferencesKey(Constants.PREF_SECURE_MODE)
|
||||||
|
private val HAPTIC_FEEDBACK = booleanPreferencesKey(Constants.PREF_HAPTIC_FEEDBACK)
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
val dynamicColorFlow: Flow<Boolean> = dataStore.data.map { preferences ->
|
||||||
|
preferences[DYNAMIC_COLOR] ?: Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val paletteStyleFlow = dataStore.data.map { preferences ->
|
||||||
|
preferences[PALETTE_STYLE] ?: Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val darkModeFlow = dataStore.data.map { preferences ->
|
||||||
|
preferences[DARK_MODE] ?: Constants.PREF_DARK_MODE_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val contrastLevelFlow = dataStore.data.map { preferences ->
|
||||||
|
preferences[CONTRAST_LEVEL] ?: Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val showCompletedFlow: Flow<Boolean> = dataStore.data.map { preferences ->
|
||||||
|
preferences[SHOW_COMPLETED] ?: Constants.PREF_SHOW_COMPLETED_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortingMethodFlow: Flow<Int> = dataStore.data.map { preferences ->
|
||||||
|
preferences[SORTING_METHOD] ?: Constants.PREF_SORTING_METHOD_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val secureModeFlow: Flow<Boolean> = dataStore.data.map { preferences ->
|
||||||
|
preferences[SECURE_MODE] ?: Constants.PREF_SECURE_MODE_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
val hapticFeedbackFlow: Flow<Boolean> = dataStore.data.map { preferences ->
|
||||||
|
preferences[HAPTIC_FEEDBACK] ?: Constants.PREF_HAPTIC_FEEDBACK_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
suspend fun setDynamicColor(value: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[DYNAMIC_COLOR] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setPaletteStyle(value: Int) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[PALETTE_STYLE] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setDarkMode(value: Int) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[DARK_MODE] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setContrastLevel(value: Float) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[CONTRAST_LEVEL] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setShowCompleted(value: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[SHOW_COMPLETED] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setSortingMethod(value: Int) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[SORTING_METHOD] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setSecureMode(value: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[SECURE_MODE] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setHapticFeedback(value: Boolean) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[HAPTIC_FEEDBACK] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,18 +8,19 @@ import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.constants.GlobalValues
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.ui.pages.crash.CrashPage
|
import cn.super12138.todo.ui.pages.crash.CrashPage
|
||||||
import cn.super12138.todo.ui.theme.DarkMode
|
import cn.super12138.todo.ui.theme.DarkMode
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.Dark
|
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.FollowSystem
|
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.Light
|
|
||||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||||
import cn.super12138.todo.ui.theme.ToDoTheme
|
import cn.super12138.todo.ui.theme.ToDoTheme
|
||||||
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
class CrashActivity : ComponentActivity() {
|
class CrashActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -40,11 +41,16 @@ class CrashActivity : ComponentActivity() {
|
||||||
val crashLogs = intent.getStringExtra("crash_logs")
|
val crashLogs = intent.getStringExtra("crash_logs")
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val darkMode = DarkMode.fromId(GlobalValues.darkMode)
|
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
|
||||||
val darkTheme = when (darkMode) {
|
val paletteStyle by DataStoreManager.paletteStyleFlow.collectAsState(initial = Constants.PREF_PALETTE_STYLE_DEFAULT)
|
||||||
FollowSystem -> isSystemInDarkTheme()
|
val contrastLevel by DataStoreManager.contrastLevelFlow.collectAsState(initial = Constants.PREF_CONTRAST_LEVEL_DEFAULT)
|
||||||
Light -> false
|
val darkMode by DataStoreManager.darkModeFlow.collectAsState(initial = Constants.PREF_DARK_MODE_DEFAULT)
|
||||||
Dark -> true
|
val hapticFeedback by DataStoreManager.hapticFeedbackFlow.collectAsState(initial = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT)
|
||||||
|
|
||||||
|
val darkTheme = when (DarkMode.fromId(darkMode)) {
|
||||||
|
DarkMode.FollowSystem -> isSystemInDarkTheme()
|
||||||
|
DarkMode.Light -> false
|
||||||
|
DarkMode.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
|
||||||
|
|
@ -55,11 +61,15 @@ class CrashActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(hapticFeedback) {
|
||||||
|
VibrationUtils.setEnabled(hapticFeedback)
|
||||||
|
}
|
||||||
|
|
||||||
ToDoTheme(
|
ToDoTheme(
|
||||||
darkTheme = darkTheme,
|
darkTheme = darkTheme,
|
||||||
style = PaletteStyle.fromId(GlobalValues.paletteStyle),
|
style = PaletteStyle.fromId(paletteStyle),
|
||||||
contrastLevel = GlobalValues.contrastLevel.toDouble(),
|
contrastLevel = contrastLevel.toDouble(),
|
||||||
dynamicColor = GlobalValues.dynamicColor
|
dynamicColor = dynamicColor
|
||||||
) {
|
) {
|
||||||
CrashPage(
|
CrashPage(
|
||||||
crashLog = crashLogs ?: stringResource(R.string.tip_no_crash_logs),
|
crashLog = crashLogs ?: stringResource(R.string.tip_no_crash_logs),
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,21 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.ui.components.Konfetti
|
import cn.super12138.todo.ui.components.Konfetti
|
||||||
import cn.super12138.todo.ui.navigation.TodoNavigation
|
import cn.super12138.todo.ui.navigation.TodoNavigation
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.Dark
|
import cn.super12138.todo.ui.theme.DarkMode
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.FollowSystem
|
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||||
import cn.super12138.todo.ui.theme.DarkMode.Light
|
|
||||||
import cn.super12138.todo.ui.theme.ToDoTheme
|
import cn.super12138.todo.ui.theme.ToDoTheme
|
||||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||||
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -34,23 +38,32 @@ class MainActivity : ComponentActivity() {
|
||||||
setContent {
|
setContent {
|
||||||
val mainViewModel: MainViewModel = viewModel()
|
val mainViewModel: MainViewModel = viewModel()
|
||||||
val showConfetti = mainViewModel.showConfetti
|
val showConfetti = mainViewModel.showConfetti
|
||||||
|
// 主题
|
||||||
|
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
|
||||||
|
val paletteStyle by DataStoreManager.paletteStyleFlow.collectAsState(initial = Constants.PREF_PALETTE_STYLE_DEFAULT)
|
||||||
|
val contrastLevel by DataStoreManager.contrastLevelFlow.collectAsState(initial = Constants.PREF_CONTRAST_LEVEL_DEFAULT)
|
||||||
|
val darkMode by DataStoreManager.darkModeFlow.collectAsState(initial = Constants.PREF_DARK_MODE_DEFAULT)
|
||||||
|
val secureMode by DataStoreManager.secureModeFlow.collectAsState(initial = Constants.PREF_SECURE_MODE_DEFAULT)
|
||||||
|
val hapticFeedback by DataStoreManager.hapticFeedbackFlow.collectAsState(initial = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT)
|
||||||
|
|
||||||
// 深色模式
|
// 深色模式
|
||||||
val darkTheme = when (mainViewModel.appDarkMode) {
|
val darkTheme = when (DarkMode.fromId(darkMode)) {
|
||||||
FollowSystem -> isSystemInDarkTheme()
|
DarkMode.FollowSystem -> isSystemInDarkTheme()
|
||||||
Light -> false
|
DarkMode.Light -> false
|
||||||
Dark -> true
|
DarkMode.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(darkMode) {
|
||||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||||
isAppearanceLightStatusBars = !darkTheme
|
isAppearanceLightStatusBars = !darkTheme
|
||||||
isAppearanceLightNavigationBars = !darkTheme
|
isAppearanceLightNavigationBars = !darkTheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻止截屏相关配置
|
// 阻止截屏相关配置
|
||||||
LaunchedEffect(mainViewModel.appSecureMode) {
|
LaunchedEffect(secureMode) {
|
||||||
if (mainViewModel.appSecureMode) {
|
if (secureMode) {
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
WindowManager.LayoutParams.FLAG_SECURE
|
WindowManager.LayoutParams.FLAG_SECURE
|
||||||
|
|
@ -60,11 +73,15 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(hapticFeedback) {
|
||||||
|
VibrationUtils.setEnabled(hapticFeedback)
|
||||||
|
}
|
||||||
|
|
||||||
ToDoTheme(
|
ToDoTheme(
|
||||||
darkTheme = darkTheme,
|
darkTheme = darkTheme,
|
||||||
style = mainViewModel.appPaletteStyle,
|
style = PaletteStyle.fromId(paletteStyle),
|
||||||
contrastLevel = mainViewModel.appContrastLevel.value.toDouble(),
|
contrastLevel = contrastLevel.toDouble(),
|
||||||
dynamicColor = mainViewModel.appDynamicColorEnable
|
dynamicColor = dynamicColor
|
||||||
) {
|
) {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
TodoNavigation(
|
TodoNavigation(
|
||||||
|
|
|
||||||
|
|
@ -102,17 +102,11 @@ fun TodoNavigation(
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(TodoScreen.SettingsAppearance.name) {
|
composable(TodoScreen.SettingsAppearance.name) {
|
||||||
SettingsAppearance(
|
SettingsAppearance(onNavigateUp = { navController.navigateUp() })
|
||||||
viewModel = viewModel,
|
|
||||||
onNavigateUp = { navController.navigateUp() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(TodoScreen.SettingsInterface.name) {
|
composable(TodoScreen.SettingsInterface.name) {
|
||||||
SettingsInterface(
|
SettingsInterface(onNavigateUp = { navController.navigateUp() })
|
||||||
viewModel = viewModel,
|
|
||||||
onNavigateUp = { navController.navigateUp() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(TodoScreen.SettingsData.name) {
|
composable(TodoScreen.SettingsData.name) {
|
||||||
|
|
@ -135,9 +129,7 @@ fun TodoNavigation(
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(TodoScreen.SettingsAboutLicence.name) {
|
composable(TodoScreen.SettingsAboutLicence.name) {
|
||||||
SettingsAboutLicence(
|
SettingsAboutLicence(onNavigateUp = { navController.navigateUp() })
|
||||||
onNavigateUp = { navController.navigateUp() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import androidx.window.core.layout.WindowWidthSizeClass
|
||||||
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.database.TodoEntity
|
import cn.super12138.todo.logic.database.TodoEntity
|
||||||
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
|
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
|
||||||
import cn.super12138.todo.ui.components.ConfirmDialog
|
import cn.super12138.todo.ui.components.ConfirmDialog
|
||||||
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
|
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
|
||||||
|
|
@ -72,7 +73,8 @@ fun MainPage(
|
||||||
|
|
||||||
val toDoList by remember { derivedStateOf { toDos.value } }
|
val toDoList by remember { derivedStateOf { toDos.value } }
|
||||||
|
|
||||||
val showCompleted = viewModel.showCompletedTodos
|
// Theme
|
||||||
|
val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT)
|
||||||
val filteredTodoList =
|
val filteredTodoList =
|
||||||
if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
|
if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ fun Candle(
|
||||||
) {
|
) {
|
||||||
// 火苗
|
// 火苗
|
||||||
Box {
|
Box {
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = showFlame,
|
visible = showFlame,
|
||||||
enter = scaleIn(),
|
enter = scaleIn(),
|
||||||
exit = scaleOut(),
|
exit = scaleOut(),
|
||||||
|
|
|
||||||
|
|
@ -10,25 +10,37 @@ import androidx.compose.material.icons.outlined.ColorLens
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
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.constants.Constants
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
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.appearance.contrast.ContrastPicker
|
import cn.super12138.todo.ui.pages.settings.components.appearance.contrast.ContrastPicker
|
||||||
import cn.super12138.todo.ui.pages.settings.components.appearance.darkmode.DarkModePicker
|
import cn.super12138.todo.ui.pages.settings.components.appearance.darkmode.DarkModePicker
|
||||||
import cn.super12138.todo.ui.pages.settings.components.appearance.palette.PalettePicker
|
import cn.super12138.todo.ui.pages.settings.components.appearance.palette.PalettePicker
|
||||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
import cn.super12138.todo.ui.theme.ContrastLevel
|
||||||
|
import cn.super12138.todo.ui.theme.DarkMode
|
||||||
|
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsAppearance(
|
fun SettingsAppearance(
|
||||||
viewModel: MainViewModel,
|
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
|
||||||
|
val darkMode by DataStoreManager.darkModeFlow.collectAsState(initial = Constants.PREF_DARK_MODE_DEFAULT)
|
||||||
|
val paletteStyle by DataStoreManager.paletteStyleFlow.collectAsState(initial = Constants.PREF_PALETTE_STYLE_DEFAULT)
|
||||||
|
val contrastLevel by DataStoreManager.contrastLevelFlow.collectAsState(initial = Constants.PREF_CONTRAST_LEVEL_DEFAULT)
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
LargeTopAppBarScaffold(
|
LargeTopAppBarScaffold(
|
||||||
title = stringResource(R.string.pref_appearance),
|
title = stringResource(R.string.pref_appearance),
|
||||||
|
|
@ -43,23 +55,30 @@ fun SettingsAppearance(
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
SwitchSettingsItem(
|
SwitchSettingsItem(
|
||||||
key = Constants.PREF_DYNAMIC_COLOR,
|
checked = dynamicColor,
|
||||||
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT,
|
|
||||||
leadingIcon = Icons.Outlined.ColorLens,
|
leadingIcon = Icons.Outlined.ColorLens,
|
||||||
title = stringResource(R.string.pref_appearance_dynamic_color),
|
title = stringResource(R.string.pref_appearance_dynamic_color),
|
||||||
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
|
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
|
||||||
onCheckedChange = { viewModel.setDynamicColor(it) },
|
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
|
||||||
)
|
)
|
||||||
|
|
||||||
DarkModePicker(onDarkModeChange = { viewModel.setDarkMode(it) })
|
DarkModePicker(
|
||||||
|
currentDarkMode = DarkMode.fromId(darkMode),
|
||||||
|
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
|
||||||
|
)
|
||||||
|
|
||||||
PalettePicker(
|
PalettePicker(
|
||||||
isDarkMode = viewModel.appDarkMode,
|
currentPalette = PaletteStyle.fromId(paletteStyle),
|
||||||
contrastLevel = viewModel.appContrastLevel,
|
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
|
||||||
onPaletteChange = { viewModel.setPaletteStyle(it) }
|
isDynamicColor = dynamicColor,
|
||||||
|
isDarkMode = DarkMode.fromId(darkMode),
|
||||||
|
contrastLevel = ContrastLevel.fromFloat(contrastLevel)
|
||||||
)
|
)
|
||||||
|
|
||||||
ContrastPicker(onContrastChange = { viewModel.setContrastLevel(it) })
|
ContrastPicker(
|
||||||
|
currentContrast = ContrastLevel.fromFloat(contrastLevel),
|
||||||
|
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,9 +13,11 @@ import androidx.compose.material.icons.outlined.Vibration
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -24,6 +26,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.constants.Constants
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.logic.model.SortingMethod
|
import cn.super12138.todo.logic.model.SortingMethod
|
||||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
|
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
|
||||||
|
|
@ -32,17 +35,21 @@ import cn.super12138.todo.ui.pages.settings.components.SettingsPlainBox
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioDialog
|
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioDialog
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioOptions
|
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioOptions
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
|
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
|
||||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
import kotlinx.coroutines.launch
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsInterface(
|
fun SettingsInterface(
|
||||||
viewModel: MainViewModel,
|
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT)
|
||||||
|
val secureMode by DataStoreManager.secureModeFlow.collectAsState(initial = Constants.PREF_SECURE_MODE_DEFAULT)
|
||||||
|
val sortingMethod by DataStoreManager.sortingMethodFlow.collectAsState(initial = Constants.PREF_SORTING_METHOD_DEFAULT)
|
||||||
|
val hapticFeedback by DataStoreManager.hapticFeedbackFlow.collectAsState(initial = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT)
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) }
|
var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
LargeTopAppBarScaffold(
|
LargeTopAppBarScaffold(
|
||||||
|
|
@ -59,38 +66,34 @@ fun SettingsInterface(
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
SettingsCategory(stringResource(R.string.pref_category_todo_list))
|
SettingsCategory(stringResource(R.string.pref_category_todo_list))
|
||||||
|
|
||||||
SwitchSettingsItem(
|
SwitchSettingsItem(
|
||||||
key = Constants.PREF_SHOW_COMPLETED,
|
checked = showCompleted,
|
||||||
default = Constants.PREF_SHOW_COMPLETED_DEFAULT,
|
|
||||||
leadingIcon = Icons.Outlined.Checklist,
|
leadingIcon = Icons.Outlined.Checklist,
|
||||||
title = stringResource(R.string.pref_show_completed),
|
title = stringResource(R.string.pref_show_completed),
|
||||||
description = stringResource(R.string.pref_show_completed_desc),
|
description = stringResource(R.string.pref_show_completed_desc),
|
||||||
onCheckedChange = { viewModel.setShowCompleted(it) },
|
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
|
||||||
)
|
)
|
||||||
SettingsItem(
|
SettingsItem(
|
||||||
leadingIcon = Icons.AutoMirrored.Outlined.Sort,
|
leadingIcon = Icons.AutoMirrored.Outlined.Sort,
|
||||||
title = stringResource(R.string.pref_sorting_method),
|
title = stringResource(R.string.pref_sorting_method),
|
||||||
description = viewModel.appSortingMethod.getDisplayName(context),
|
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
|
||||||
onClick = { showSortingMethodDialog = true }
|
onClick = { showSortingMethodDialog = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingsCategory(stringResource(R.string.pref_category_global))
|
SettingsCategory(stringResource(R.string.pref_category_global))
|
||||||
SwitchSettingsItem(
|
SwitchSettingsItem(
|
||||||
key = Constants.PREF_SECURE_MODE,
|
checked = secureMode,
|
||||||
default = Constants.PREF_SECURE_MODE_DEFAULT,
|
|
||||||
leadingIcon = Icons.Outlined.Shield,
|
leadingIcon = Icons.Outlined.Shield,
|
||||||
title = stringResource(R.string.pref_secure_mode),
|
title = stringResource(R.string.pref_secure_mode),
|
||||||
description = stringResource(R.string.pref_secure_mode_desc),
|
description = stringResource(R.string.pref_secure_mode_desc),
|
||||||
onCheckedChange = { viewModel.setSecureMode(it) }
|
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }
|
||||||
)
|
)
|
||||||
SwitchSettingsItem(
|
SwitchSettingsItem(
|
||||||
key = Constants.PREF_HAPTIC_FEEDBACK,
|
checked = hapticFeedback,
|
||||||
default = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT,
|
|
||||||
leadingIcon = Icons.Outlined.Vibration,
|
leadingIcon = Icons.Outlined.Vibration,
|
||||||
title = stringResource(R.string.pref_haptic_feedback),
|
title = stringResource(R.string.pref_haptic_feedback),
|
||||||
description = stringResource(R.string.pref_haptic_feedback_desc),
|
description = stringResource(R.string.pref_haptic_feedback_desc),
|
||||||
onCheckedChange = { VibrationUtils.setEnabled(it) }
|
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }
|
||||||
)
|
)
|
||||||
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
|
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
|
||||||
}
|
}
|
||||||
|
|
@ -105,14 +108,14 @@ fun SettingsInterface(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsRadioDialog(
|
SettingsRadioDialog(
|
||||||
key = Constants.PREF_SORTING_METHOD,
|
|
||||||
defaultIndex = Constants.PREF_SORTING_METHOD_DEFAULT,
|
|
||||||
visible = showSortingMethodDialog,
|
visible = showSortingMethodDialog,
|
||||||
title = stringResource(R.string.pref_sorting_method),
|
title = stringResource(R.string.pref_sorting_method),
|
||||||
|
currentOptions = SettingsRadioOptions(
|
||||||
|
id = sortingMethod,
|
||||||
|
text = SortingMethod.fromId(sortingMethod).getDisplayName(context)
|
||||||
|
),
|
||||||
options = sortingList,
|
options = sortingList,
|
||||||
onSelect = { id ->
|
onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } },
|
||||||
viewModel.setSortingMethod(SortingMethod.fromId(id))
|
|
||||||
},
|
|
||||||
onDismiss = { showSortingMethodDialog = false }
|
onDismiss = { showSortingMethodDialog = false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -5,14 +5,14 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.foundation.selection.selectableGroup
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -21,15 +21,13 @@ import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import cn.super12138.todo.ui.TodoDefaults
|
import cn.super12138.todo.ui.TodoDefaults
|
||||||
import cn.super12138.todo.ui.components.BasicDialog
|
import cn.super12138.todo.ui.components.BasicDialog
|
||||||
import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsRadioDialog(
|
fun SettingsRadioDialog(
|
||||||
key: String,
|
|
||||||
defaultIndex: Int,
|
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
title: String,
|
title: String,
|
||||||
|
currentOptions: SettingsRadioOptions,
|
||||||
options: List<SettingsRadioOptions>,
|
options: List<SettingsRadioOptions>,
|
||||||
onSelect: (id: Int) -> Unit,
|
onSelect: (id: Int) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
|
|
@ -39,15 +37,17 @@ fun SettingsRadioDialog(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
title = title,
|
title = title,
|
||||||
text = {
|
text = {
|
||||||
var selectedItemIndex by rememberPrefIntState(key, defaultIndex)
|
|
||||||
// Modifier.selectableGroup() 用来确保无障碍功能运行正确
|
// Modifier.selectableGroup() 用来确保无障碍功能运行正确
|
||||||
Column(Modifier.selectableGroup()) {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.selectableGroup()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
options.forEach { option ->
|
options.forEach { option ->
|
||||||
RadioItem(
|
RadioItem(
|
||||||
selected = option.id == selectedItemIndex,
|
selected = option == currentOptions,
|
||||||
text = option.text,
|
text = option.text,
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedItemIndex = option.id
|
|
||||||
onSelect(option.id)
|
onSelect(option.id)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -96,11 +96,6 @@ fun RadioItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SettingsRadioOptions(
|
|
||||||
val id: Int,
|
|
||||||
val text: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDialog(
|
fun SettingsDialog(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import cn.super12138.todo.ui.TodoDefaults
|
import cn.super12138.todo.ui.TodoDefaults
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package cn.super12138.todo.ui.pages.settings.components
|
||||||
|
|
||||||
|
data class SettingsRadioOptions(
|
||||||
|
val id: Int,
|
||||||
|
val text: String,
|
||||||
|
)
|
||||||
|
|
@ -3,36 +3,30 @@ package cn.super12138.todo.ui.pages.settings.components
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
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 androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import cn.super12138.todo.ui.TodoDefaults
|
import cn.super12138.todo.ui.TodoDefaults
|
||||||
import cn.super12138.todo.ui.pages.settings.state.rememberPrefBooleanState
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchSettingsItem(
|
fun SwitchSettingsItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
key: String,
|
checked: Boolean,
|
||||||
default: Boolean,
|
|
||||||
leadingIcon: ImageVector? = null,
|
leadingIcon: ImageVector? = null,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
onCheckedChange: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
var switchState by rememberPrefBooleanState(key, default)
|
|
||||||
SettingsItem(
|
SettingsItem(
|
||||||
leadingIcon = leadingIcon,
|
leadingIcon = leadingIcon,
|
||||||
title = title,
|
title = title,
|
||||||
description = description,
|
description = description,
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = switchState,
|
checked = checked,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
switchState = it
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
VibrationUtils.performHapticFeedback(view)
|
||||||
onCheckedChange(it)
|
onCheckedChange(it)
|
||||||
},
|
},
|
||||||
|
|
@ -40,8 +34,7 @@ fun SwitchSettingsItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
switchState = !switchState
|
onCheckedChange(!checked)
|
||||||
onCheckedChange(switchState)
|
|
||||||
},
|
},
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package cn.super12138.todo.ui.pages.settings.components.appearance.contrast
|
package cn.super12138.todo.ui.pages.settings.components.appearance.contrast
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -13,6 +14,8 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -26,14 +29,13 @@ import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.semantics.stateDescription
|
import androidx.compose.ui.semantics.stateDescription
|
||||||
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.ui.pages.settings.components.MoreContentSettingsItem
|
import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem
|
||||||
import cn.super12138.todo.ui.pages.settings.state.rememberPrefFloatState
|
|
||||||
import cn.super12138.todo.ui.theme.ContrastLevel
|
import cn.super12138.todo.ui.theme.ContrastLevel
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ContrastPicker(
|
fun ContrastPicker(
|
||||||
|
currentContrast: ContrastLevel,
|
||||||
onContrastChange: (ContrastLevel) -> Unit,
|
onContrastChange: (ContrastLevel) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
@ -44,27 +46,31 @@ fun ContrastPicker(
|
||||||
description = stringResource(R.string.pref_contrast_level_desc),
|
description = stringResource(R.string.pref_contrast_level_desc),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
var contrastState by rememberPrefFloatState(
|
|
||||||
Constants.PREF_CONTRAST_LEVEL,
|
|
||||||
Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
|
||||||
)
|
|
||||||
val contrastLevelName =
|
val contrastLevelName =
|
||||||
ContrastLevel.entries.map { it.getDisplayName(context) }
|
ContrastLevel.entries.map { it.getDisplayName(context) }
|
||||||
|
var lastVibratedLevel by remember { mutableFloatStateOf(currentContrast.value) }
|
||||||
|
|
||||||
Slider(
|
Slider(
|
||||||
modifier = Modifier.semantics {
|
modifier = Modifier.semantics {
|
||||||
contentDescription =
|
contentDescription =
|
||||||
context.getString(R.string.pref_contrast_level) + contrastLevelName[ContrastLevel.fromFloat(
|
context.getString(R.string.pref_contrast_level) + contrastLevelName[currentContrast.ordinal]
|
||||||
contrastState
|
stateDescription = contrastLevelName[currentContrast.ordinal]
|
||||||
).ordinal]
|
|
||||||
stateDescription = contrastLevelName[ContrastLevel.fromFloat(contrastState).ordinal]
|
|
||||||
liveRegion = LiveRegionMode.Polite
|
liveRegion = LiveRegionMode.Polite
|
||||||
},
|
},
|
||||||
value = contrastState,
|
value = currentContrast.value,
|
||||||
onValueChange = {
|
onValueChange = { newValue ->
|
||||||
VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS)
|
// 更新状态
|
||||||
contrastState = it
|
onContrastChange(ContrastLevel.fromFloat(newValue))
|
||||||
onContrastChange(ContrastLevel.fromFloat(it))
|
|
||||||
|
// 只有当分段值变化时才触发震动
|
||||||
|
if (newValue != lastVibratedLevel) {
|
||||||
|
VibrationUtils.performHapticFeedback(
|
||||||
|
view,
|
||||||
|
HapticFeedbackConstants.LONG_PRESS
|
||||||
|
)
|
||||||
|
Log.d("ContrastPicker", "Level changed to: ${newValue}")
|
||||||
|
lastVibratedLevel = newValue
|
||||||
|
}
|
||||||
},
|
},
|
||||||
valueRange = -1f..1f,
|
valueRange = -1f..1f,
|
||||||
steps = 3,
|
steps = 3,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import cn.super12138.todo.utils.VibrationUtils
|
||||||
@Composable
|
@Composable
|
||||||
fun DarkModeItem(
|
fun DarkModeItem(
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
contentDescription: String,
|
name: String,
|
||||||
contentColor: Color,
|
contentColor: Color,
|
||||||
containerColor: Color,
|
containerColor: Color,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
|
|
@ -73,7 +73,7 @@ fun DarkModeItem(
|
||||||
Spacer(Modifier.size(8.dp))
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = contentDescription,
|
text = name,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,23 @@ package cn.super12138.todo.ui.pages.settings.components.appearance.darkmode
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
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.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.theme.DarkMode
|
import cn.super12138.todo.ui.theme.DarkMode
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DarkModePicker(
|
fun DarkModePicker(
|
||||||
|
currentDarkMode: DarkMode,
|
||||||
onDarkModeChange: (darkMode: DarkMode) -> Unit,
|
onDarkModeChange: (darkMode: DarkMode) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val isInDarkTheme = isSystemInDarkTheme()
|
val isInDarkTheme = isSystemInDarkTheme()
|
||||||
var darkModeState by rememberPrefIntState(
|
|
||||||
Constants.PREF_DARK_MODE,
|
|
||||||
Constants.PREF_DARK_MODE_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
RowSettingsItem(
|
RowSettingsItem(
|
||||||
title = stringResource(R.string.pref_dark_mode),
|
title = stringResource(R.string.pref_dark_mode),
|
||||||
description = stringResource(R.string.pref_dark_mode_desc),
|
description = stringResource(R.string.pref_dark_mode_desc),
|
||||||
|
|
@ -38,38 +29,29 @@ fun DarkModePicker(
|
||||||
) {
|
) {
|
||||||
DarkModeItem(
|
DarkModeItem(
|
||||||
icon = DarkMode.FollowSystem.icon,
|
icon = DarkMode.FollowSystem.icon,
|
||||||
contentDescription = DarkMode.FollowSystem.getDisplayName(context),
|
name = DarkMode.FollowSystem.getDisplayName(context),
|
||||||
contentColor = if (isInDarkTheme) Color.White else Color.Black,
|
contentColor = if (isInDarkTheme) Color.White else Color.Black,
|
||||||
containerColor = if (isInDarkTheme) Color.Black else Color.White,
|
containerColor = if (isInDarkTheme) Color.Black else Color.White,
|
||||||
selected = DarkMode.fromId(darkModeState) == DarkMode.FollowSystem,
|
selected = currentDarkMode == DarkMode.FollowSystem,
|
||||||
onSelect = {
|
onSelect = { onDarkModeChange(DarkMode.FollowSystem) }
|
||||||
darkModeState = DarkMode.FollowSystem.id
|
|
||||||
onDarkModeChange(DarkMode.FollowSystem)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DarkModeItem(
|
DarkModeItem(
|
||||||
icon = DarkMode.Light.icon,
|
icon = DarkMode.Light.icon,
|
||||||
contentDescription = DarkMode.Light.getDisplayName(context),
|
name = DarkMode.Light.getDisplayName(context),
|
||||||
contentColor = Color.Black,
|
contentColor = Color.Black,
|
||||||
containerColor = Color.White,
|
containerColor = Color.White,
|
||||||
selected = DarkMode.fromId(darkModeState) == DarkMode.Light,
|
selected = currentDarkMode == DarkMode.Light,
|
||||||
onSelect = {
|
onSelect = { onDarkModeChange(DarkMode.Light) }
|
||||||
darkModeState = DarkMode.Light.id
|
|
||||||
onDarkModeChange(DarkMode.Light)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DarkModeItem(
|
DarkModeItem(
|
||||||
icon = DarkMode.Dark.icon,
|
icon = DarkMode.Dark.icon,
|
||||||
contentDescription = DarkMode.Dark.getDisplayName(context),
|
name = DarkMode.Dark.getDisplayName(context),
|
||||||
contentColor = Color.White,
|
contentColor = Color.White,
|
||||||
containerColor = Color.Black,
|
containerColor = Color.Black,
|
||||||
selected = DarkMode.fromId(darkModeState) == DarkMode.Dark,
|
selected = currentDarkMode == DarkMode.Dark,
|
||||||
onSelect = {
|
onSelect = { onDarkModeChange(DarkMode.Dark) }
|
||||||
darkModeState = DarkMode.Dark.id
|
|
||||||
onDarkModeChange(DarkMode.Dark)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +28,6 @@ import androidx.compose.ui.res.colorResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
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.ui.theme.ContrastLevel
|
import cn.super12138.todo.ui.theme.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
|
||||||
|
|
@ -36,6 +35,7 @@ import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PaletteItem(
|
fun PaletteItem(
|
||||||
|
isDynamicColor: Boolean,
|
||||||
isDark: Boolean,
|
isDark: Boolean,
|
||||||
paletteStyle: PaletteStyle,
|
paletteStyle: PaletteStyle,
|
||||||
contrastLevel: ContrastLevel,
|
contrastLevel: ContrastLevel,
|
||||||
|
|
@ -63,7 +63,7 @@ fun PaletteItem(
|
||||||
// 为不同主题样式设置不同色板
|
// 为不同主题样式设置不同色板
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = dynamicColorScheme(
|
colorScheme = dynamicColorScheme(
|
||||||
keyColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && GlobalValues.dynamicColor) {
|
keyColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && isDynamicColor) {
|
||||||
colorResource(id = android.R.color.system_accent1_500)
|
colorResource(id = android.R.color.system_accent1_500)
|
||||||
} else {
|
} else {
|
||||||
Color(0xFF0061A4)
|
Color(0xFF0061A4)
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,25 @@ package cn.super12138.todo.ui.pages.settings.components.appearance.palette
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
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.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.theme.ContrastLevel
|
import cn.super12138.todo.ui.theme.ContrastLevel
|
||||||
import cn.super12138.todo.ui.theme.DarkMode
|
import cn.super12138.todo.ui.theme.DarkMode
|
||||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PalettePicker(
|
fun PalettePicker(
|
||||||
|
currentPalette: PaletteStyle,
|
||||||
|
onPaletteChange: (paletteStyle: PaletteStyle) -> Unit,
|
||||||
|
isDynamicColor: Boolean,
|
||||||
isDarkMode: DarkMode,
|
isDarkMode: DarkMode,
|
||||||
contrastLevel: ContrastLevel,
|
contrastLevel: ContrastLevel,
|
||||||
onPaletteChange: (paletteStyle: PaletteStyle) -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var paletteState by rememberPrefIntState(
|
|
||||||
Constants.PREF_PALETTE_STYLE,
|
|
||||||
Constants.PREF_PALETTE_STYLE_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
val paletteOptions = remember {
|
val paletteOptions = remember {
|
||||||
PaletteStyle.entries.toList()
|
PaletteStyle.entries.toList()
|
||||||
|
|
@ -42,18 +36,16 @@ fun PalettePicker(
|
||||||
) {
|
) {
|
||||||
paletteOptions.forEach { paletteStyle ->
|
paletteOptions.forEach { paletteStyle ->
|
||||||
PaletteItem(
|
PaletteItem(
|
||||||
|
isDynamicColor = isDynamicColor,
|
||||||
isDark = when (isDarkMode) {
|
isDark = when (isDarkMode) {
|
||||||
DarkMode.FollowSystem -> isSystemInDarkTheme()
|
DarkMode.FollowSystem -> isSystemInDarkTheme()
|
||||||
DarkMode.Light -> false
|
DarkMode.Light -> false
|
||||||
DarkMode.Dark -> true
|
DarkMode.Dark -> true
|
||||||
},
|
},
|
||||||
paletteStyle = paletteStyle,
|
paletteStyle = paletteStyle,
|
||||||
selected = PaletteStyle.fromId(paletteState) == paletteStyle,
|
selected = currentPalette == paletteStyle,
|
||||||
contrastLevel = contrastLevel,
|
contrastLevel = contrastLevel,
|
||||||
onSelect = {
|
onSelect = { onPaletteChange(paletteStyle) }
|
||||||
paletteState = paletteStyle.id
|
|
||||||
onPaletteChange(paletteStyle)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
|
|
@ -98,7 +97,7 @@ fun LicenceItem(
|
||||||
modifier = Modifier.padding(padding.licensePadding.contentPadding),
|
modifier = Modifier.padding(padding.licensePadding.contentPadding),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
text = it.name,
|
text = it.name,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
// style = MaterialTheme.typography.labelSmall,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
overflow = LibraryDefaults.libraryTextStyles().defaultOverflow,
|
overflow = LibraryDefaults.libraryTextStyles().defaultOverflow,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
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.MutableFloatState
|
|
||||||
import androidx.compose.runtime.MutableIntState
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.core.content.edit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 来自: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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberPrefFloatState(key: String, default: Float): MutableFloatState {
|
|
||||||
val context = LocalContext.current
|
|
||||||
return remember { PrefMutableFloatState(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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun component1(): Int {
|
|
||||||
return delegate.component1()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun component2(): (Int) -> Unit {
|
|
||||||
return delegate.component2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
* @author hushenghao
|
|
||||||
*/
|
|
||||||
val Context.pref: SharedPreferences
|
|
||||||
get() {
|
|
||||||
return applicationContext.getSharedPreferences(
|
|
||||||
applicationContext.packageName + "_preferences",
|
|
||||||
Context.MODE_PRIVATE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -9,19 +9,18 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import cn.super12138.todo.TodoApp
|
import cn.super12138.todo.TodoApp
|
||||||
import cn.super12138.todo.constants.Constants
|
import cn.super12138.todo.constants.Constants
|
||||||
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.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.logic.model.SortingMethod
|
import cn.super12138.todo.logic.model.SortingMethod
|
||||||
import cn.super12138.todo.ui.theme.ContrastLevel
|
|
||||||
import cn.super12138.todo.ui.theme.DarkMode
|
|
||||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
|
||||||
import cn.super12138.todo.utils.FileUtils
|
import cn.super12138.todo.utils.FileUtils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -33,37 +32,27 @@ import java.io.FileOutputStream
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
// 待办
|
// 待办
|
||||||
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||||
var appSortingMethod by mutableStateOf(SortingMethod.fromId(GlobalValues.sortingMethod))
|
val sortedTodos: Flow<List<TodoEntity>> =
|
||||||
val sortedTodos: Flow<List<TodoEntity>> = toDos.map { list ->
|
DataStoreManager.sortingMethodFlow.flatMapLatest { sortingMethod ->
|
||||||
when (appSortingMethod) {
|
toDos.map { list ->
|
||||||
SortingMethod.Sequential -> list.sortedBy { it.id }
|
when (SortingMethod.fromId(sortingMethod)) {
|
||||||
SortingMethod.Subject -> list.sortedBy { it.subject }
|
SortingMethod.Sequential -> list.sortedBy { it.id }
|
||||||
SortingMethod.Priority -> list.sortedByDescending { it.priority } // 优先级高的在前
|
SortingMethod.Subject -> list.sortedBy { it.subject }
|
||||||
SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前
|
SortingMethod.Priority -> list.sortedByDescending { it.priority } // 优先级高的在前
|
||||||
SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content }
|
SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前
|
||||||
SortingMethod.AlphabeticalDescending -> list.sortedByDescending { it.content }
|
SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content }
|
||||||
|
SortingMethod.AlphabeticalDescending -> list.sortedByDescending { it.content }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val showConfetti = mutableStateOf(false)
|
val showConfetti = mutableStateOf(false)
|
||||||
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
||||||
private set
|
private set
|
||||||
var showCompletedTodos by mutableStateOf(GlobalValues.showCompleted)
|
|
||||||
|
|
||||||
// 主题颜色
|
|
||||||
var appDynamicColorEnable by mutableStateOf(GlobalValues.dynamicColor)
|
|
||||||
private set
|
|
||||||
var appPaletteStyle by mutableStateOf(PaletteStyle.fromId(GlobalValues.paletteStyle))
|
|
||||||
private set
|
|
||||||
var appDarkMode by mutableStateOf(DarkMode.fromId(GlobalValues.darkMode))
|
|
||||||
private set
|
|
||||||
var appContrastLevel by mutableStateOf(ContrastLevel.fromFloat(GlobalValues.contrastLevel))
|
|
||||||
private set
|
|
||||||
var appSecureMode by mutableStateOf(GlobalValues.secureMode)
|
|
||||||
private set
|
|
||||||
|
|
||||||
// 多选逻辑参考:https://github.com/X1nto/Mauth
|
// 多选逻辑参考:https://github.com/X1nto/Mauth
|
||||||
private val _selectedTodoIds = MutableStateFlow(listOf<Int>())
|
private val _selectedTodoIds = MutableStateFlow(listOf<Int>())
|
||||||
|
|
@ -147,36 +136,12 @@ class MainViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用设置
|
* 备份应用数据
|
||||||
|
*
|
||||||
|
* @param uri 备份文件路径的 URI
|
||||||
|
* @param context 应用 Context
|
||||||
|
* @param onResult 备份完成的回调函数
|
||||||
*/
|
*/
|
||||||
fun setDynamicColor(enabled: Boolean) {
|
|
||||||
appDynamicColorEnable = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPaletteStyle(paletteStyle: PaletteStyle) {
|
|
||||||
appPaletteStyle = paletteStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDarkMode(darkMode: DarkMode) {
|
|
||||||
appDarkMode = darkMode
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setContrastLevel(contrastLevel: ContrastLevel) {
|
|
||||||
appContrastLevel = contrastLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShowCompleted(show: Boolean) {
|
|
||||||
showCompletedTodos = show
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSortingMethod(sortingMethod: SortingMethod) {
|
|
||||||
appSortingMethod = sortingMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSecureMode(enabled: Boolean) {
|
|
||||||
appSecureMode = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fun backupAppData(uri: Uri, context: Context, onResult: (completed: Boolean) -> Unit) {
|
fun backupAppData(uri: Uri, context: Context, onResult: (completed: Boolean) -> Unit) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
|
|
@ -192,6 +157,13 @@ class MainViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复应用数据
|
||||||
|
*
|
||||||
|
* @param uri 选择的恢复文件的 URI
|
||||||
|
* @param context 应用 Context
|
||||||
|
* @param onResult 恢复完成的回调函数
|
||||||
|
*/
|
||||||
fun restoreAppData(uri: Uri, context: Context, onResult: (completed: Boolean) -> Unit) {
|
fun restoreAppData(uri: Uri, context: Context, onResult: (completed: Boolean) -> Unit) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
|
|
@ -205,23 +177,36 @@ class MainViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要备份文件的文件列表
|
||||||
|
* * 数据库文件
|
||||||
|
* * 数据库的 wal 文件
|
||||||
|
* * 数据库的 shm 文件
|
||||||
|
* * DataStore Preferences 文件
|
||||||
|
*/
|
||||||
private fun getBackupFiles(context: Context): List<File> {
|
private fun getBackupFiles(context: Context): List<File> {
|
||||||
val dbPath = TodoApp.db.openHelper.writableDatabase.path
|
val dbPath = TodoApp.db.openHelper.writableDatabase.path
|
||||||
val prefPath = "${context.filesDir.parent}/shared_prefs"
|
val prefPath = "${context.filesDir}/datastore"
|
||||||
return listOf(
|
return listOf(
|
||||||
context.getDatabasePath(Constants.DB_NAME), // 数据库
|
context.getDatabasePath(Constants.DB_NAME), // 数据库
|
||||||
File("$dbPath-wal"), // 数据库-wal
|
File("$dbPath-wal"), // 数据库-wal
|
||||||
File("$dbPath-shm"), // 数据库-shm
|
File("$dbPath-shm"), // 数据库-shm
|
||||||
File("$prefPath/${Constants.SP_NAME}.xml") // SharedPreferences
|
File("$prefPath/${Constants.SP_NAME}.preferences_pb") // DataStore Preferences
|
||||||
).filter { it.exists() }
|
).filter { it.exists() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压 zip 备份文件
|
||||||
|
*
|
||||||
|
* @param zipInputStream 备份文件中每个文件的输入流
|
||||||
|
* @param context 应用 Context
|
||||||
|
*/
|
||||||
private fun extractZipEntries(zipInputStream: ZipInputStream, context: Context) {
|
private fun extractZipEntries(zipInputStream: ZipInputStream, context: Context) {
|
||||||
val dbPath = context.getDatabasePath(Constants.DB_NAME).parent
|
val dbPath = context.getDatabasePath(Constants.DB_NAME).parent
|
||||||
val prefPath = "${context.filesDir.parent}/shared_prefs/"
|
val prefPath = "${context.filesDir}/datastore/"
|
||||||
generateSequence { zipInputStream.nextEntry }.forEach { zipEntry ->
|
generateSequence { zipInputStream.nextEntry }.forEach { zipEntry ->
|
||||||
val outputFile = File(
|
val outputFile = File(
|
||||||
if (zipEntry.name.endsWith(".xml")) prefPath else dbPath,
|
if (zipEntry.name.endsWith(".preferences_pb")) prefPath else dbPath,
|
||||||
zipEntry.name
|
zipEntry.name
|
||||||
)
|
)
|
||||||
if (zipEntry.isDirectory) {
|
if (zipEntry.isDirectory) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,10 +2,10 @@ package cn.super12138.todo.utils
|
||||||
|
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import cn.super12138.todo.constants.GlobalValues
|
import cn.super12138.todo.constants.Constants
|
||||||
|
|
||||||
object VibrationUtils {
|
object VibrationUtils {
|
||||||
private var isEnabled: Boolean = GlobalValues.hapticFeedback
|
private var isEnabled: Boolean = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT
|
||||||
|
|
||||||
fun setEnabled(enabled: Boolean) {
|
fun setEnabled(enabled: Boolean) {
|
||||||
isEnabled = enabled
|
isEnabled = enabled
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
coreKtx = "1.16.0"
|
coreKtx = "1.16.0"
|
||||||
splashScreen = "1.2.0-beta02"
|
splashScreen = "1.2.0-beta02"
|
||||||
lifecycleRuntimeKtx = "2.9.1"
|
lifecycleRuntimeKtx = "2.9.1"
|
||||||
# security = "1.1.0-alpha06"
|
datastore = "1.1.7"
|
||||||
# Compose
|
# Compose
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2025.06.01"
|
composeBom = "2025.06.01"
|
||||||
|
|
@ -41,7 +41,7 @@ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecyc
|
||||||
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "liveData" }
|
androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "liveData" }
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
# androidx-security = { group = "androidx.security", name = "security-crypto", version.ref = "security" }
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue