From a3f4c628d0848cd383a0ffab14f8f0e62a542865 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Tue, 11 Feb 2025 21:30:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(settings):=20=E5=AE=89=E5=85=A8=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同时将主题逻辑移入 ViewModel 崩溃界面支持主题色板、动态颜色和对比度 提取部分方法 --- .../cn/super12138/todo/constants/Constants.kt | 3 ++ .../super12138/todo/constants/GlobalValues.kt | 4 ++ .../todo/ui/activities/CrashActivity.kt | 8 ++- .../todo/ui/activities/MainActivity.kt | 16 +++++- .../ui/pages/settings/SettingsAppearance.kt | 6 +-- .../settings/SettingsInterfaceInteraction.kt | 11 ++++- .../cn/super12138/todo/ui/theme/Theme.kt | 11 +---- .../todo/ui/viewmodels/MainViewModel.kt | 49 +++++++++---------- .../cn/super12138/todo/utils/FileUtils.kt | 30 ++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- 11 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/utils/FileUtils.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 8f56131..f646298 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -29,6 +29,9 @@ object Constants { const val PREF_SORTING_METHOD = "sorting_method" const val PREF_SORTING_METHOD_DEFAULT = 1 + const val PREF_SECURE_MODE = "secure_mode" + const val PREF_SECURE_MODE_DEFAULT = false + const val PREF_HAPTIC_FEEDBACK = "haptic_feedback" const val PREF_HAPTIC_FEEDBACK_DEFAULT = true } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt index aa4630c..53c5fb6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt @@ -27,6 +27,10 @@ object GlobalValues { 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 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt index 798a1f3..2e900c3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt @@ -18,6 +18,7 @@ import cn.super12138.todo.logic.model.DarkMode.Dark import cn.super12138.todo.logic.model.DarkMode.FollowSystem import cn.super12138.todo.logic.model.DarkMode.Light import cn.super12138.todo.ui.pages.crash.CrashPage +import cn.super12138.todo.ui.theme.PaletteStyle import cn.super12138.todo.ui.theme.ToDoTheme import java.text.SimpleDateFormat import java.util.Calendar @@ -74,7 +75,12 @@ class CrashActivity : ComponentActivity() { } } - ToDoTheme(darkTheme = darkTheme) { + ToDoTheme( + darkTheme = darkTheme, + style = PaletteStyle.fromId(GlobalValues.paletteStyle), + contrastLevel = GlobalValues.contrastLevel.toDouble(), + dynamicColor = GlobalValues.dynamicColor + ) { CrashPage( crashLog = if (crashLogs == null) stringResource(R.string.tip_no_crash_logs) else crashLogFormatted, exitApp = { finishAffinity() }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt index 86bb871..ec1705b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt @@ -1,6 +1,7 @@ package cn.super12138.todo.ui.activities import android.os.Bundle +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -44,9 +45,22 @@ class MainActivity : ComponentActivity() { } } + LaunchedEffect(mainViewModel.appSecureMode) { + if (mainViewModel.appSecureMode) { + window.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } + ToDoTheme( darkTheme = darkTheme, - contrastLevel = mainViewModel.appContrastLevel.value.toDouble() + style = mainViewModel.appPaletteStyle, + contrastLevel = mainViewModel.appContrastLevel.value.toDouble(), + dynamicColor = mainViewModel.appDynamicColorEnable ) { Surface(color = MaterialTheme.colorScheme.background) { TodoNavigation( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt index 414df85..ca7e51b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAppearance.kt @@ -20,8 +20,6 @@ import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem import cn.super12138.todo.ui.pages.settings.components.contrast.ContrastPicker import cn.super12138.todo.ui.pages.settings.components.darkmode.DarkModePicker import cn.super12138.todo.ui.pages.settings.components.palette.PalettePicker -import cn.super12138.todo.ui.theme.appPaletteStyle -import cn.super12138.todo.ui.theme.isDynamicColorEnable import cn.super12138.todo.ui.viewmodels.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -49,7 +47,7 @@ fun SettingsAppearance( PalettePicker( isDarkMode = viewModel.appDarkMode, contrastLevel = viewModel.appContrastLevel, - onPaletteChange = { appPaletteStyle = it } + onPaletteChange = { viewModel.setPaletteStyle(it) } ) ContrastPicker(onContrastChange = { viewModel.setContrastLevel(it) }) @@ -60,7 +58,7 @@ fun SettingsAppearance( leadingIcon = Icons.Outlined.ColorLens, title = stringResource(R.string.pref_appearance_dynamic_color), description = stringResource(R.string.pref_appearance_dynamic_color_desc), - onCheckedChange = { isDynamicColorEnable = it }, + onCheckedChange = { viewModel.setDynamicColor(it) }, ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index fbd6bb4..72722cd 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Sort import androidx.compose.material.icons.outlined.Checklist +import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.Vibration import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults @@ -74,7 +75,15 @@ fun SettingsInterface( onClick = { showSortingMethodDialog = true } ) - SettingsCategory(stringResource(R.string.pref_category_global_interaction)) + SettingsCategory(stringResource(R.string.pref_category_global)) + SwitchSettingsItem( + key = Constants.PREF_SECURE_MODE, + default = Constants.PREF_SECURE_MODE_DEFAULT, + leadingIcon = Icons.Outlined.Shield, + title = stringResource(R.string.pref_secure_mode), + description = stringResource(R.string.pref_secure_mode_desc), + onCheckedChange = { viewModel.setSecureMode(it) } + ) SwitchSettingsItem( key = Constants.PREF_HAPTIC_FEEDBACK, default = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt index e97059c..cfbd8c7 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt @@ -4,24 +4,17 @@ 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 = appPaletteStyle, + style: PaletteStyle = PaletteStyle.TonalSpot, contrastLevel: Double = 0.0, // Dynamic color is available on Android 12+ - dynamicColor: Boolean = isDynamicColorEnable, + dynamicColor: Boolean = true, content: @Composable () -> Unit ) { val baseColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && dynamicColor) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index a5604e4..de4a852 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -15,6 +15,8 @@ import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.model.ContrastLevel import cn.super12138.todo.logic.model.DarkMode import cn.super12138.todo.logic.model.SortingMethod +import cn.super12138.todo.ui.theme.PaletteStyle +import cn.super12138.todo.utils.FileUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,7 +29,6 @@ import kotlinx.coroutines.withContext import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.File -import java.io.FileInputStream import java.io.FileOutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -54,10 +55,16 @@ class MainViewModel : ViewModel() { 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 private val _selectedTodoIds = MutableStateFlow(listOf()) @@ -150,6 +157,14 @@ class MainViewModel : ViewModel() { /** * 应用设置 */ + fun setDynamicColor(enabled: Boolean) { + appDynamicColorEnable = enabled + } + + fun setPaletteStyle(paletteStyle: PaletteStyle) { + appPaletteStyle = paletteStyle + } + fun setDarkMode(darkMode: DarkMode) { appDarkMode = darkMode } @@ -166,6 +181,10 @@ class MainViewModel : ViewModel() { appSortingMethod = sortingMethod } + fun setSecureMode(enabled: Boolean) { + appSecureMode = enabled + } + fun backupDatabase(uri: Uri, context: Context, onResult: (completed: Boolean) -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { @@ -178,9 +197,9 @@ class MainViewModel : ViewModel() { val dbWal = File("$dbPath-wal") val dbShm = File("$dbPath-shm") - addFileToZip(dbFile, dbFile.name, zipOutStream) - addFileToZip(dbWal, dbWal.name, zipOutStream) - addFileToZip(dbShm, dbShm.name, zipOutStream) + FileUtils.addFileToZip(dbFile, dbFile.name, zipOutStream) + FileUtils.addFileToZip(dbWal, dbWal.name, zipOutStream) + FileUtils.addFileToZip(dbShm, dbShm.name, zipOutStream) } } @@ -230,26 +249,4 @@ class MainViewModel : ViewModel() { } } } - - private fun addFileToZip(file: File, fileName: String, zipOut: ZipOutputStream) { - if (file.isHidden) { - return // 忽略隐藏文件 - } - if (file.isDirectory) { - // 如果是文件夹,递归处理 - val children = file.listFiles() - if (children != null) { - for (childFile in children) { - addFileToZip(childFile, "$fileName/${childFile.name}", zipOut) - } - } - } else { - // 如果是文件,写入 ZIP - FileInputStream(file).use { fis -> - val zipEntry = ZipEntry(fileName) - zipOut.putNextEntry(zipEntry) - fis.copyTo(zipOut) - } - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/FileUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/FileUtils.kt new file mode 100644 index 0000000..80ddcdc --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/utils/FileUtils.kt @@ -0,0 +1,30 @@ +package cn.super12138.todo.utils + +import java.io.File +import java.io.FileInputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +object FileUtils { + fun addFileToZip(file: File, fileName: String, zipOut: ZipOutputStream) { + if (file.isHidden) { + return // 忽略隐藏文件 + } + if (file.isDirectory) { + // 如果是文件夹,递归处理 + val children = file.listFiles() + if (children != null) { + for (childFile in children) { + addFileToZip(childFile, "$fileName/${childFile.name}", zipOut) + } + } + } else { + // 如果是文件,写入 ZIP + FileInputStream(file).use { fis -> + val zipEntry = ZipEntry(fileName) + zipOut.putNextEntry(zipEntry) + fis.copyTo(zipOut) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2b1f602..22a5788 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -89,7 +89,7 @@ 首字母(升序) 首字母(降序) 排序方式 - 全局交互 + 全局设置 触感反馈 为某些操作提供轻微的震动反馈 提示 @@ -104,4 +104,6 @@ 备份失败 数据恢复成功,需要重启应用以加载数据 恢复失败 + 安全模式 + 阻止截屏并保护后台预览图 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72f8b21..f1da624 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,7 +90,7 @@ Alphabetical (Ascending) Alphabetical (Descending) Sorting Method - Global Interaction + Global Settings Haptic Feedback Provide slight vibration feedback for some operations Tips @@ -105,4 +105,6 @@ Backup failed Restore successful, restart the app to load data Restore failed + Secure Mode + Prevent screenshots and protect the background preview image \ No newline at end of file