From 29d71fc0607f5c65244729e3e4374b1acf980d08 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:51:49 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E5=AE=9E=E7=8E=B0=20#163?= =?UTF-8?q?=20#167?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 +- .../4.json | 55 +++++++++ .../cn/super12138/todo/constants/Constants.kt | 7 +- .../todo/logic/database/TodoDatabase.kt | 18 ++- .../todo/logic/database/TodoEntity.kt | 3 +- .../todo/logic/datastore/DataStoreManager.kt | 15 +++ .../todo/logic/model/SortingMethod.kt | 4 +- .../super12138/todo/logic/model/Subjects.kt | 40 ------- .../todo/ui/components/ChipGroup.kt | 21 +++- .../super12138/todo/ui/components/Dialogs.kt | 69 ++++++++++++ .../todo/ui/navigation/TodoNavigation.kt | 6 + .../todo/ui/navigation/TodoScreen.kt | 1 + .../todo/ui/pages/editor/TodoEditorPage.kt | 95 ++++++++++------ .../editor/components/TodoEditorTextFields.kt | 4 +- .../todo/ui/pages/editor/state/EditorState.kt | 48 +++----- .../super12138/todo/ui/pages/main/MainPage.kt | 4 +- .../todo/ui/pages/main/ManagerFragment.kt | 12 +- .../todo/ui/pages/main/components/TodoCard.kt | 6 +- .../todo/ui/pages/settings/SettingsData.kt | 13 +++ .../ui/pages/settings/SettingsDataCategory.kt | 104 ++++++++++++++++++ .../todo/ui/viewmodels/MainViewModel.kt | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 27 ++--- app/src/main/res/values/strings.xml | 27 ++--- build.gradle.kts | 1 + gradle/libs.versions.toml | 7 +- 25 files changed, 421 insertions(+), 174 deletions(-) create mode 100644 app/schemas/cn.super12138.todo.logic.database.TodoDatabase/4.json delete mode 100644 app/src/main/kotlin/cn/super12138/todo/logic/model/Subjects.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7c35dd6..4f648af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) alias(libs.plugins.aboutlibraries) } @@ -34,7 +35,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 735 + versionCode = 742 versionName = "2.1.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -108,9 +109,10 @@ dependencies { implementation(libs.nl.dionsegijn.konfetti.compose) // Lazy Column Scrollbar implementation(libs.lazycolumnscrollbar) - // Kotlin Coroutines + // Kotlin implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.serialization.json) // Room implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) diff --git a/app/schemas/cn.super12138.todo.logic.database.TodoDatabase/4.json b/app/schemas/cn.super12138.todo.logic.database.TodoDatabase/4.json new file mode 100644 index 0000000..7103b67 --- /dev/null +++ b/app/schemas/cn.super12138.todo.logic.database.TodoDatabase/4.json @@ -0,0 +1,55 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "80864d24cabaf6ae6bfa6debb235a034", + "entities": [ + { + "tableName": "todo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`content` TEXT NOT NULL, `category` TEXT NOT NULL, `completed` INTEGER NOT NULL, `priority` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isCompleted", + "columnName": "completed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '80864d24cabaf6ae6bfa6debb235a034')" + ] + } +} \ No newline at end of file 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 c448422..616a5d0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -6,7 +6,7 @@ object Constants { const val KEY_TODO_FAB_TRANSITION = "todo_fab" const val KEY_TODO_CONTENT_TRANSITION = "todo_content" - const val KEY_TODO_SUBJECT_TRANSITION = "todo_subject" + const val KEY_TODO_CATEGORY_TRANSITION = "todo_category" const val DB_NAME = "todo" const val DB_TABLE_NAME = "todo" @@ -36,4 +36,9 @@ object Constants { const val PREF_HAPTIC_FEEDBACK = "haptic_feedback" const val PREF_HAPTIC_FEEDBACK_DEFAULT = true + + const val PREF_CATEGORIES = "categories" + const val PREF_CATEGORIES_DEFAULT = "[]" + + } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt index eb4d58c..3dfcdc7 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt @@ -8,7 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import cn.super12138.todo.constants.Constants -@Database(entities = [TodoEntity::class], version = 3) +@Database(entities = [TodoEntity::class], version = 4) abstract class TodoDatabase : RoomDatabase() { abstract fun toDoDao(): TodoDao @@ -22,7 +22,7 @@ abstract class TodoDatabase : RoomDatabase() { TodoDatabase::class.java, Constants.DB_NAME ) - .addMigrations(MIGRATION_2_3) + .addMigrations(MIGRATION_2_3, MIGRATION_3_4) .fallbackToDestructiveMigration(false) .build() @@ -36,5 +36,19 @@ abstract class TodoDatabase : RoomDatabase() { db.execSQL("ALTER TABLE ${Constants.DB_TABLE_NAME} ADD COLUMN custom_subject TEXT NOT NULL DEFAULT ''") } } + + // 为自定义学科功能进行迁移 + private val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + // 创建一个新表,其中不含有subject,并且有一个新的category字段(由custom_subject迁移而来) + db.execSQL("CREATE TABLE IF NOT EXISTS todo_new (content TEXT NOT NULL, category TEXT NOT NULL DEFAULT 'Default Value', completed INTEGER NOT NULL, priority REAL NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") + // 将旧表中的数据迁移到新表中 + db.execSQL("INSERT INTO todo_new (content, category, completed, priority, id) SELECT content, COALESCE(NULLIF(custom_subject, ''), 'Default Value') AS category, completed, priority, id FROM todo") + // 删除旧表 + db.execSQL("DROP TABLE todo") + // 重命名新表 + db.execSQL("ALTER TABLE todo_new RENAME TO todo") + } + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt index cf43621..1174db1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt @@ -8,8 +8,7 @@ import cn.super12138.todo.constants.Constants @Entity(tableName = Constants.DB_TABLE_NAME) data class TodoEntity( @ColumnInfo(name = "content") val content: String, - @ColumnInfo(name = "subject") val subject: Int, - @ColumnInfo(name = "custom_subject") val customSubject: String = "", + @ColumnInfo(name = "category") val category: String = "", @ColumnInfo(name = "completed") val isCompleted: Boolean = false, @ColumnInfo(name = "priority") val priority: Float, @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0, diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/datastore/DataStoreManager.kt b/app/src/main/kotlin/cn/super12138/todo/logic/datastore/DataStoreManager.kt index 27be618..093a097 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/datastore/DataStoreManager.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/datastore/DataStoreManager.kt @@ -6,11 +6,13 @@ 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.core.stringPreferencesKey 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 +import kotlinx.serialization.json.Json object DataStoreManager { private val Context.dataStore by preferencesDataStore( @@ -40,6 +42,9 @@ object DataStoreManager { private val SECURE_MODE = booleanPreferencesKey(Constants.PREF_SECURE_MODE) private val HAPTIC_FEEDBACK = booleanPreferencesKey(Constants.PREF_HAPTIC_FEEDBACK) + // 数据 + private val CATEGORIES = stringPreferencesKey(Constants.PREF_CATEGORIES) + // Getters val dynamicColorFlow: Flow = dataStore.data.map { preferences -> preferences[DYNAMIC_COLOR] ?: Constants.PREF_DYNAMIC_COLOR_DEFAULT @@ -73,6 +78,10 @@ object DataStoreManager { preferences[HAPTIC_FEEDBACK] ?: Constants.PREF_HAPTIC_FEEDBACK_DEFAULT } + val categoriesFlow: Flow> = dataStore.data.map { preferences -> + Json.decodeFromString(preferences[CATEGORIES] ?: Constants.PREF_CATEGORIES_DEFAULT) + } + // Setters suspend fun setDynamicColor(value: Boolean) { dataStore.edit { preferences -> @@ -121,4 +130,10 @@ object DataStoreManager { preferences[HAPTIC_FEEDBACK] = value } } + + suspend fun setCategories(value: List) { + dataStore.edit { preferences -> + preferences[CATEGORIES] = Json.encodeToString(value) + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt index c2e44c5..2c73a4c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt @@ -8,7 +8,7 @@ enum class SortingMethod(val id: Int) { Sequential(1), // 按学科 - Subject(2), + Category(2), // 按优先级 Priority(3), @@ -25,7 +25,7 @@ enum class SortingMethod(val id: Int) { fun getDisplayName(context: Context): String { val resId = when (this) { Sequential -> R.string.sorting_sequential - Subject -> R.string.sorting_subject + Category -> R.string.sorting_category Priority -> R.string.sorting_priority Completion -> R.string.sorting_completion AlphabeticalAscending -> R.string.sorting_alphabetical_ascending diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/Subjects.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/Subjects.kt deleted file mode 100644 index df097ff..0000000 --- a/app/src/main/kotlin/cn/super12138/todo/logic/model/Subjects.kt +++ /dev/null @@ -1,40 +0,0 @@ -package cn.super12138.todo.logic.model - -import android.content.Context -import cn.super12138.todo.R - -enum class Subjects(val id: Int) { - Chinese(0), - Math(1), - English(2), - Biology(3), - Geography(4), - Physics(5), - Moral(6), - Chemistry(7), - History(8), - Others(99), - Custom(100); - - fun getDisplayName(context: Context): String { - val resId = when (this) { - Chinese -> R.string.subject_chinese - Math -> R.string.subject_math - English -> R.string.subject_english - Biology -> R.string.subject_biology - Geography -> R.string.subject_geography - Physics -> R.string.subject_physics - Moral -> R.string.subject_moral - Chemistry -> R.string.subject_chemistry - History -> R.string.subject_history - Others -> R.string.subject_others - Custom -> R.string.subject_customization - } - return context.getString(resId) // 返回资源中的文本 - } - - companion object { - // 根据 ID 获取 Subjects - fun fromId(id: Int) = entries.find { it.id == id } ?: Others - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt index f8b7524..70531b1 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.components +import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandIn import androidx.compose.animation.fadeIn @@ -16,6 +17,8 @@ import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -27,6 +30,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils +import kotlin.math.log /** * 部分参考:https://github.com/Rhythamtech/FilterChipGroup-Compose-Android/blob/main/FilterChipGroup.kt @@ -39,16 +43,23 @@ fun FilterChipGroup( defaultSelectedItemIndex: Int = 0, onSelectedChanged: (Int) -> Unit = {} ) { + SideEffect { + Log.d("TAG", "来自 FilterChipGroup:重组啦") + } val view = LocalView.current - var selectedItemId by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) } + var selectedItemIndex by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) } + + LaunchedEffect(defaultSelectedItemIndex) { + selectedItemIndex = defaultSelectedItemIndex + } FlowRow(modifier = modifier) { items.forEach { item -> FilterChipItem( - selected = item.id == selectedItemId, - text = item.text, + selected = item.id == selectedItemIndex, + text = item.name, onClick = { - selectedItemId = item.id + selectedItemIndex = item.id VibrationUtils.performHapticFeedback(view) onSelectedChanged(item.id) } @@ -87,5 +98,5 @@ private fun FilterChipItem( data class ChipItem( val id: Int, - val text: String + val name: String ) \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index baf89df..3263b45 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -2,15 +2,24 @@ package cn.super12138.todo.ui.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ErrorOutline +import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.AlertDialog import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView @@ -19,6 +28,66 @@ import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils +@Composable +fun FiveCharPromptDialog( + modifier: Modifier = Modifier, + visible: Boolean, + icon: ImageVector = Icons.Outlined.Info, + title: String = stringResource(R.string.tip_tips), + text: String, + confirmButtonText: String = stringResource(R.string.action_save), + showDismissButton: Boolean = true, + dismissButtonText: String = stringResource(R.string.action_cancel), + properties: DialogProperties = DialogProperties(), + onSave: (String) -> Unit, + onDismiss: () -> Unit +) { + val textFieldState = rememberTextFieldState() + var isError by rememberSaveable { mutableStateOf(false) } + + val supportingText = listOf( + stringResource(R.string.tip_max_length_5), + stringResource(R.string.error_no_content_entered), + stringResource(R.string.error_exceeds_5_chars) + ) + + var currentSupportingText by remember { mutableStateOf(supportingText[0]) } + + BasicDialog( + visible = visible, + icon = icon, + title = title, + text = { // 已经是实现好滚动的Column布局 + Text(text) + OutlinedTextField( + state = textFieldState, + lineLimits = TextFieldLineLimits.SingleLine, + label = { Text(stringResource(R.string.label_enter_sth)) }, + supportingText = { Text(currentSupportingText) }, + isError = isError + ) + }, + confirmButton = confirmButtonText, + dismissButton = if (showDismissButton) dismissButtonText else null, + onConfirm = { + if (textFieldState.text.trim().isEmpty()) { + isError = true + currentSupportingText = supportingText[1] + return@BasicDialog + } else if (textFieldState.text.trim().length > 5) { + isError = true + currentSupportingText = supportingText[2] + } else { + onSave(textFieldState.text.toString()) + onDismiss() + } + }, + onDismiss = onDismiss, + properties = properties, + modifier = modifier + ) +} + @Composable fun ConfirmDialog( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index f89e7b7..69c7025 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -14,6 +14,7 @@ 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.SettingsData +import cn.super12138.todo.ui.pages.settings.SettingsDataCategory import cn.super12138.todo.ui.pages.settings.SettingsInterface import cn.super12138.todo.ui.pages.settings.SettingsMain import cn.super12138.todo.ui.theme.materialSharedAxisXIn @@ -111,10 +112,15 @@ fun TodoNavigation( composable(TodoScreen.SettingsData.name) { SettingsData( viewModel = viewModel, + toCategoryManager = {navController.navigate(TodoScreen.SettingsDataCategory.name)}, onNavigateUp = { navController.navigateUp() } ) } + composable(TodoScreen.SettingsDataCategory.name) { + SettingsDataCategory(onNavigateUp = {navController.navigateUp()}) + } + composable(TodoScreen.SettingsAbout.name) { SettingsAbout( //toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index b6df581..16f7d7e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -7,6 +7,7 @@ enum class TodoScreen { SettingsAppearance, SettingsInterface, SettingsData, + SettingsDataCategory, SettingsAbout, //SettingsAboutSpecial, SettingsAboutLicence diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index a235bf0..7cca560 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.editor +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope @@ -26,28 +27,32 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity -import cn.super12138.todo.logic.model.Subjects +import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider -import cn.super12138.todo.ui.pages.editor.components.TodoSubjectTextField import cn.super12138.todo.ui.pages.editor.state.rememberEditorState import cn.super12138.todo.utils.VibrationUtils @@ -63,11 +68,40 @@ fun TodoEditorPage( animatedVisibilityScope: AnimatedVisibilityScope ) { val view = LocalView.current - val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val uiState = rememberEditorState(initialTodo = toDo) - val isCustomSubject by remember { derivedStateOf { uiState.selectedSubjectId == Subjects.Custom.id } } + + val originalCategories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList()) + val categories = originalCategories + .mapIndexed { index, category -> + ChipItem( + id = index, + name = category + ) + } + ChipItem(id = -1, name = "自定义") + + var defaultIndex by remember { mutableIntStateOf(0) } + LaunchedEffect(originalCategories, toDo) { + if (toDo == null) { + val index = if (categories.size == 1) -1 else 0 + defaultIndex = index + uiState.selectedCategoryIndex = index + } else { + val index = categories.firstOrNull { it.name == toDo.category }?.id ?: -1 + defaultIndex = index + uiState.selectedCategoryIndex = index + if (index == -1) { + uiState.categoryContent = toDo.category + } + } + } + + val isCustomCategory by remember { + derivedStateOf { + uiState.selectedCategoryIndex == -1 + } + } fun checkModifiedBeforeBack() { if (uiState.isModified()) { @@ -77,9 +111,7 @@ fun TodoEditorPage( } } - BackHandler { - checkModifiedBeforeBack() - } + BackHandler { checkModifiedBeforeBack() } LargeTopAppBarScaffold( title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), @@ -106,7 +138,14 @@ fun TodoEditorPage( return@AnimatedExtendedFloatingActionButton } else { uiState.clearError() - onSave(uiState.getEntity()) + val newTodo = TodoEntity( + id = toDo?.id ?: 0, + content = uiState.toDoContent, + category = if (isCustomCategory) uiState.categoryContent else categories[uiState.selectedCategoryIndex].name, + priority = uiState.priorityState, + isCompleted = uiState.isCompleted + ) + onSave(newTodo) } }, modifier = Modifier @@ -137,50 +176,34 @@ fun TodoEditorPage( isError = uiState.isErrorContent, modifier = Modifier .fillMaxWidth() - .sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"), - animatedVisibilityScope = animatedVisibilityScope - ) ) } } item { Text( - text = stringResource(R.string.label_subject), + text = stringResource(R.string.label_category), style = MaterialTheme.typography.titleMedium ) - val subjects = remember { - Subjects.entries.map { - ChipItem( - id = it.id, - text = it.getDisplayName(context) - ) - } - } FilterChipGroup( - items = subjects, - defaultSelectedItemIndex = toDo?.subject ?: Subjects.Chinese.id, - onSelectedChanged = { uiState.selectedSubjectId = it }, + items = categories, + defaultSelectedItemIndex = defaultIndex, + onSelectedChanged = { uiState.selectedCategoryIndex = it }, modifier = Modifier.fillMaxWidth() ) + AnimatedVisibility( - visible = isCustomSubject, + visible = isCustomCategory, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { with(sharedTransitionScope) { - TodoSubjectTextField( - value = uiState.subjectContent, - onValueChange = { uiState.subjectContent = it }, - isError = uiState.isErrorSubject, - modifier = Modifier - .fillMaxWidth() - .sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_${toDo?.id}"), - animatedVisibilityScope = animatedVisibilityScope - ) + TodoCategoryTextField( + value = uiState.categoryContent, + onValueChange = { uiState.categoryContent = it }, + isError = uiState.isErrorCategory, + modifier = Modifier.fillMaxWidth() ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt index 44dd0a5..68ea089 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt @@ -30,7 +30,7 @@ fun TodoContentTextField( } @Composable -fun TodoSubjectTextField( +fun TodoCategoryTextField( value: String, onValueChange: (String) -> Unit, isError: Boolean, @@ -39,7 +39,7 @@ fun TodoSubjectTextField( TextField( value = value, onValueChange = onValueChange, - label = { Text(stringResource(R.string.label_enter_subject_name)) }, + label = { Text(stringResource(R.string.label_enter_category_name)) }, isError = isError, supportingText = { AnimatedVisibility(isError) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt index ce8a09f..46795c6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt @@ -9,18 +9,16 @@ import androidx.compose.runtime.saveable.SaverScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import cn.super12138.todo.logic.database.TodoEntity -import cn.super12138.todo.logic.model.Subjects -class EditorState( - val initialTodo: TodoEntity? = null, -) { +class EditorState(val initialTodo: TodoEntity? = null) { var toDoContent by mutableStateOf(initialTodo?.content ?: "") var isErrorContent by mutableStateOf(false) - var selectedSubjectId by mutableIntStateOf(initialTodo?.subject ?: 0) - var subjectContent by mutableStateOf(initialTodo?.customSubject ?: "") - var isErrorSubject by mutableStateOf(false) + var selectedCategoryIndex by mutableIntStateOf(0) + var categoryContent by mutableStateOf(initialTodo?.category ?: "") + var isErrorCategory by mutableStateOf(false) var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f) var isCompleted by mutableStateOf(initialTodo?.isCompleted == true) + var showExitConfirmDialog by mutableStateOf(false) var showDeleteConfirmDialog by mutableStateOf(false) @@ -31,9 +29,8 @@ class EditorState( */ fun setErrorIfNotValid(): Boolean { isErrorContent = toDoContent.trim().isEmpty() - isErrorSubject = subjectContent.trim().isEmpty() && - selectedSubjectId == Subjects.Custom.id - return isErrorContent || isErrorSubject + isErrorCategory = if (selectedCategoryIndex == -1) categoryContent.trim().isEmpty() else false + return isErrorContent || isErrorCategory } /** @@ -41,31 +38,16 @@ class EditorState( */ fun clearError() { isErrorContent = false - isErrorSubject = false + isErrorCategory = false } - /** - * 获取编辑后的待办实体 - * - * @return TodoEntity 待办实体 - */ - fun getEntity(): TodoEntity = TodoEntity( - id = initialTodo?.id ?: 0, - content = toDoContent, - subject = selectedSubjectId, - customSubject = subjectContent, - priority = priorityState, - isCompleted = isCompleted - ) - /** * 检查待办是否被编辑修改 */ fun isModified(): Boolean { var isModified = false if ((initialTodo?.content ?: "") != toDoContent) isModified = true - if ((initialTodo?.subject ?: 0) != selectedSubjectId) isModified = true - if ((initialTodo?.customSubject ?: "") != subjectContent) isModified = true + if ((initialTodo?.category ?: "") != categoryContent) isModified = true if ((initialTodo?.priority ?: 0f) != priorityState) isModified = true if ((initialTodo?.isCompleted == true) != isCompleted) isModified = true return isModified @@ -80,9 +62,9 @@ class EditorState( value.initialTodo?.id ?: 0, value.toDoContent, value.isErrorContent, - value.selectedSubjectId, - value.subjectContent, - value.isErrorSubject, + value.selectedCategoryIndex, + value.categoryContent, + value.isErrorCategory, value.priorityState, value.isCompleted, value.showExitConfirmDialog, @@ -96,9 +78,9 @@ class EditorState( return EditorState(initialTodo).apply { toDoContent = list[1] as String isErrorContent = list[2] as Boolean - selectedSubjectId = list[3] as Int - subjectContent = list[4] as String - isErrorSubject = list[5] as Boolean + selectedCategoryIndex = list[3] as Int + categoryContent = list[4] as String + isErrorCategory = list[5] as Boolean priorityState = list[6] as Float isCompleted = list[7] as Boolean showExitConfirmDialog = list[8] as Boolean diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index d7ba026..bbda843 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -150,7 +150,7 @@ fun MainPage( viewModel.updateTodo( TodoEntity( content = content, - subject = subject, + category = category, isCompleted = true, priority = priority, id = id @@ -193,7 +193,7 @@ fun MainPage( viewModel.updateTodo( TodoEntity( content = content, - subject = subject, + category = category, isCompleted = true, priority = priority, id = id diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index be83f7d..0c57b44 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -13,14 +13,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.model.Priority -import cn.super12138.todo.logic.model.Subjects import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar import cn.super12138.todo.ui.pages.main.components.TodoCard @@ -38,8 +36,6 @@ fun ManagerFragment( sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ) { - val context = LocalContext.current - LazyColumnCustomScrollBar( state = state, modifier = modifier @@ -67,16 +63,10 @@ fun ManagerFragment( items = list, key = { it.id } ) { item -> - val subject = if (item.subject == Subjects.Custom.id) { - item.customSubject - } else { - Subjects.fromId(item.subject).getDisplayName(context) - } - TodoCard( id = item.id, content = item.content, - subject = subject, + category = item.category, completed = item.isCompleted, priority = Priority.fromFloat(item.priority), selected = selectedTodoIds.contains(item.id), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index 4d2b953..2c35b61 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -49,7 +49,7 @@ fun TodoCard( modifier: Modifier = Modifier, id: Int, content: String, - subject: String, + category: String, completed: Boolean, priority: Priority, selected: Boolean, @@ -141,12 +141,12 @@ fun TodoCard( with(sharedTransitionScope) { Text( - text = subject, + text = category, style = MaterialTheme.typography.labelMedium, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, maxLines = 1, modifier = Modifier.sharedBounds( - sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_$id"), + sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"), animatedVisibilityScope = animatedVisibilityScope ) ) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index b00753b..dc86d23 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Category import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.outlined.FileUpload import androidx.compose.material.icons.outlined.RestartAlt @@ -31,6 +32,7 @@ import cn.super12138.todo.R import cn.super12138.todo.ui.activities.MainActivity import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +import cn.super12138.todo.ui.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.utils.SystemUtils @@ -41,6 +43,7 @@ import kotlin.system.exitProcess @Composable fun SettingsData( viewModel: MainViewModel, + toCategoryManager: () -> Unit, onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { @@ -114,6 +117,7 @@ fun SettingsData( .padding(innerPadding) ) { item { + SettingsCategory(stringResource(R.string.pref_category_data_management)) SettingsItem( leadingIcon = Icons.Outlined.FileDownload, title = stringResource(R.string.pref_backup), @@ -133,6 +137,15 @@ fun SettingsData( } ) } + item { + SettingsCategory(stringResource(R.string.pref_category_category_management)) + SettingsItem( + leadingIcon = Icons.Outlined.Category, + title = stringResource(R.string.pref_category_category_management), + description = stringResource(R.string.pref_category_management_desc), + onClick = toCategoryManager + ) + } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt new file mode 100644 index 0000000..99c0468 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -0,0 +1,104 @@ +package cn.super12138.todo.ui.pages.settings + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import cn.super12138.todo.R +import cn.super12138.todo.logic.datastore.DataStoreManager +import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton +import cn.super12138.todo.ui.components.FiveCharPromptDialog +import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +import cn.super12138.todo.ui.pages.settings.components.SettingsCategory +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsDataCategory( + onNavigateUp: () -> Unit, + modifier: Modifier = Modifier +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + val listState = rememberLazyListState() + val context = LocalContext.current + + var showDialog by rememberSaveable { mutableStateOf(false) } + val categories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList()) + + val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } + + LargeTopAppBarScaffold( + title = stringResource(R.string.pref_category_category_management), + onBack = onNavigateUp, + scrollBehavior = scrollBehavior, + snackbarHost = { SnackbarHost(snackbarHostState) }, + floatingActionButton = { + AnimatedExtendedFloatingActionButton( + icon = Icons.Outlined.Add, + text = stringResource(R.string.action_add_category), + expanded = isExpanded, + onClick = { showDialog = true } + ) + }, + modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + if (categories.isEmpty()) { + item { + Text("空") + } + } else { + items(items = categories, key = { it }) { + SettingsCategory(it) + } + } + } + } + + FiveCharPromptDialog( + visible = showDialog, + text = stringResource(R.string.tip_enter_category), + onSave = { + if (!categories.contains(it)) { + scope.launch { + DataStoreManager.setCategories(categories + it) + } + } else { + scope.launch { + snackbarHostState.showSnackbar( + message = context.getString(R.string.error_category_duplicate) + ) + } + } + }, + onDismiss = { showDialog = false } + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index 987988e..2f9d3b9 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 @@ -41,7 +41,7 @@ class MainViewModel : ViewModel() { toDos.map { list -> when (SortingMethod.fromId(sortingMethod)) { SortingMethod.Sequential -> list.sortedBy { it.id } - SortingMethod.Subject -> list.sortedBy { it.subject } + SortingMethod.Category -> list.sortedBy { it.category } SortingMethod.Priority -> list.sortedByDescending { it.priority } // 优先级高的在前 SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前 SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 805b94e..8b088d0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -10,16 +10,6 @@ 没有输入内容 选择该项 标记为已完成 - 语文 - 数学 - 英语 - 生物 - 地理 - 物理 - 道法 - 化学 - 历史 - 其它 修改待办 剩余 %s 项任务 应用程序出现错误 @@ -39,7 +29,7 @@ 开发者 开放源代码许可 查看应用使用的开源库及其许可 - 学科 + 类别 不紧急 不重要 默认 @@ -82,7 +72,7 @@ 查看源代码、提交错误报告和改进建议 退出编辑后将无法找回你修改过的数据。确定退出编辑吗? 添加先后顺序 - 学科 + 类别 优先级 完成状态 首字母(升序) @@ -106,9 +96,16 @@ 安全模式 阻止截屏并保护后台预览图 更多 - 待办1岁生日快乐 - 自定义 - 输入学科名称 + 输入类别名称 当前共有 %1$d 项任务,其中 %2$d 项已完成,%3$d 项未完成 当前没有任务 + 类别管理 + 数据管理 + 管理任务的类别标签 + 添加类别 + 输入内容 + 不超过 5 个字 + 超过 5 个字 + 输入你想添加的类别 + 该类别已经存在 \ 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 8e27e40..a9b4560 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,16 +9,6 @@ No content entered Select this Mark as completed - Chinese - Math - English - Biology - Geography - Physics - Morality and Rule of Law - Chemistry - History - Others Edit Task %s tasks remaining Oops! App went wrong @@ -40,7 +30,7 @@ Developer Open Source Licences Check the open source libraries used by the application and their licences. - Subject + Category Not Urgent Not Important Default @@ -83,7 +73,7 @@ View source code, submit bug reports, and improvement suggestions After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing? Sequential - Subject + category Priority Completion Alphabetical (Ascending) @@ -107,9 +97,16 @@ Secure Mode Prevent screenshots and protect the background preview image More - Happy 1st birthday to ToDo - Customization - Enter subject name + Enter category name Currently, there are %1$d tasks in total, with %2$d completed and %3$d unfinished. There are no tasks at the moment. + Category Management + Data Management + Manage the category of tasks + Add Category + Enter something + Up to 5 characters + Exceeds 5 characters + Enter the category you want to add + The category is duplicate \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7e2da06..2539732 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.aboutlibraries) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80fd40f..0626661 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,9 +21,10 @@ m3color = "2025.3" # Konfetti konfetti = "2.0.5" # Lazy Column Scrollbar -lazycolumnscrollbar = "2.2.0" +lazyColumnScrollbar = "2.2.0" # Kotlin kotlinCoroutines = "1.10.2" +kotlinSerialization = "1.9.0" # Test junit = "4.13.2" junitVersion = "1.2.1" @@ -75,11 +76,12 @@ com-kyant0-m3color = { group = "com.github.Kyant0", name = "m3color", version.re nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" } # Lazy Column Scrollbar -lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazycolumnscrollbar" } +lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazyColumnScrollbar" } # Kotlin kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinCoroutines" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinSerialization" } # Test junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -90,5 +92,6 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibsReleasePlugin" } \ No newline at end of file From 75913080ea849075664df0534430bab0eba5aa44 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:54:44 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E9=80=BB=E8=BE=91=E5=8F=8A=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20&=20=E4=BC=98=E5=8C=96=E6=B7=BB=E5=8A=A0=E7=B1=BB=E5=88=AB?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ui/components/ChipGroup.kt | 3 - .../super12138/todo/ui/components/Dialogs.kt | 69 ------------- .../todo/ui/pages/editor/TodoEditorPage.kt | 18 ++-- .../editor/components/TodoCategoryChip.kt | 41 ++++++++ .../ui/pages/settings/SettingsDataCategory.kt | 17 +++- .../components/category/CategoryDialog.kt | 98 +++++++++++++++++++ .../components/category/CategoryItem.kt | 66 +++++++++++++ 7 files changed, 226 insertions(+), 86 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt index 70531b1..ab5bffb 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt @@ -43,9 +43,6 @@ fun FilterChipGroup( defaultSelectedItemIndex: Int = 0, onSelectedChanged: (Int) -> Unit = {} ) { - SideEffect { - Log.d("TAG", "来自 FilterChipGroup:重组啦") - } val view = LocalView.current var selectedItemIndex by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index 3263b45..baf89df 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -2,24 +2,15 @@ package cn.super12138.todo.ui.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.input.TextFieldLineLimits -import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ErrorOutline -import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.AlertDialog import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView @@ -28,66 +19,6 @@ import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils -@Composable -fun FiveCharPromptDialog( - modifier: Modifier = Modifier, - visible: Boolean, - icon: ImageVector = Icons.Outlined.Info, - title: String = stringResource(R.string.tip_tips), - text: String, - confirmButtonText: String = stringResource(R.string.action_save), - showDismissButton: Boolean = true, - dismissButtonText: String = stringResource(R.string.action_cancel), - properties: DialogProperties = DialogProperties(), - onSave: (String) -> Unit, - onDismiss: () -> Unit -) { - val textFieldState = rememberTextFieldState() - var isError by rememberSaveable { mutableStateOf(false) } - - val supportingText = listOf( - stringResource(R.string.tip_max_length_5), - stringResource(R.string.error_no_content_entered), - stringResource(R.string.error_exceeds_5_chars) - ) - - var currentSupportingText by remember { mutableStateOf(supportingText[0]) } - - BasicDialog( - visible = visible, - icon = icon, - title = title, - text = { // 已经是实现好滚动的Column布局 - Text(text) - OutlinedTextField( - state = textFieldState, - lineLimits = TextFieldLineLimits.SingleLine, - label = { Text(stringResource(R.string.label_enter_sth)) }, - supportingText = { Text(currentSupportingText) }, - isError = isError - ) - }, - confirmButton = confirmButtonText, - dismissButton = if (showDismissButton) dismissButtonText else null, - onConfirm = { - if (textFieldState.text.trim().isEmpty()) { - isError = true - currentSupportingText = supportingText[1] - return@BasicDialog - } else if (textFieldState.text.trim().length > 5) { - isError = true - currentSupportingText = supportingText[2] - } else { - onSave(textFieldState.text.toString()) - onDismiss() - } - }, - onDismiss = onDismiss, - properties = properties, - modifier = modifier - ) -} - @Composable fun ConfirmDialog( modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 7cca560..43f06d7 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -1,6 +1,5 @@ package cn.super12138.todo.ui.pages.editor -import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope @@ -33,7 +32,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -48,8 +46,8 @@ import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ConfirmDialog -import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.LargeTopAppBarScaffold +import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider @@ -67,6 +65,7 @@ fun TodoEditorPage( sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ) { + // TODO: 本页及其相关组件重组性能检查优化 val view = LocalView.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -83,6 +82,7 @@ fun TodoEditorPage( var defaultIndex by remember { mutableIntStateOf(0) } LaunchedEffect(originalCategories, toDo) { + if (originalCategories.isEmpty()) return@LaunchedEffect if (toDo == null) { val index = if (categories.size == 1) -1 else 0 defaultIndex = index @@ -91,9 +91,7 @@ fun TodoEditorPage( val index = categories.firstOrNull { it.name == toDo.category }?.id ?: -1 defaultIndex = index uiState.selectedCategoryIndex = index - if (index == -1) { - uiState.categoryContent = toDo.category - } + if (index != -1) uiState.categoryContent = "" } } @@ -174,8 +172,7 @@ fun TodoEditorPage( value = uiState.toDoContent, onValueChange = { uiState.toDoContent = it }, isError = uiState.isErrorContent, - modifier = Modifier - .fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) } } @@ -186,10 +183,11 @@ fun TodoEditorPage( style = MaterialTheme.typography.titleMedium ) - FilterChipGroup( + TodoCategoryChip( items = categories, defaultSelectedItemIndex = defaultIndex, - onSelectedChanged = { uiState.selectedCategoryIndex = it }, + isLoading = originalCategories.isEmpty(), + onCategorySelected = { uiState.selectedCategoryIndex = it }, modifier = Modifier.fillMaxWidth() ) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt new file mode 100644 index 0000000..c4c5707 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt @@ -0,0 +1,41 @@ +package cn.super12138.todo.ui.pages.editor.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import cn.super12138.todo.ui.components.ChipItem +import cn.super12138.todo.ui.components.FilterChipGroup + +@Composable +fun TodoCategoryChip( + modifier: Modifier = Modifier, + items: List, + defaultSelectedItemIndex: Int, + isLoading: Boolean = false, + onCategorySelected: (Int) -> Unit +) { + Box(modifier = modifier.fillMaxWidth()) { + AnimatedContent( + targetState = isLoading, + transitionSpec = { fadeIn(tween(100)) togetherWith fadeOut(tween(100)) } + ) { + if (it) { + CircularProgressIndicator() + } else { + FilterChipGroup( + modifier = Modifier, + items = items, + defaultSelectedItemIndex = defaultSelectedItemIndex, + onSelectedChanged = onCategorySelected + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 99c0468..58e7734 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -28,9 +28,9 @@ import androidx.compose.ui.res.stringResource import cn.super12138.todo.R import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton -import cn.super12138.todo.ui.components.FiveCharPromptDialog import cn.super12138.todo.ui.components.LargeTopAppBarScaffold -import cn.super12138.todo.ui.pages.settings.components.SettingsCategory +import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem +import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -39,6 +39,7 @@ fun SettingsDataCategory( onNavigateUp: () -> Unit, modifier: Modifier = Modifier ) { + // TODO: 本页及其相关组件重组性能检查优化 val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() @@ -77,13 +78,21 @@ fun SettingsDataCategory( } } else { items(items = categories, key = { it }) { - SettingsCategory(it) + CategoryItem( + name = it, + onDelete = { it -> + scope.launch { + DataStoreManager.setCategories(categories - it) + } + }, + modifier = Modifier.animateItem() + ) } } } } - FiveCharPromptDialog( + CategoryPromptDialog( visible = showDialog, text = stringResource(R.string.tip_enter_category), onSave = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt new file mode 100644 index 0000000..671f28d --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryDialog.kt @@ -0,0 +1,98 @@ +package cn.super12138.todo.ui.pages.settings.components.category + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties +import cn.super12138.todo.R +import cn.super12138.todo.ui.components.BasicDialog + +@Composable +fun CategoryPromptDialog( + modifier: Modifier = Modifier, + visible: Boolean, + icon: ImageVector = Icons.Outlined.Info, + title: String = stringResource(R.string.tip_tips), + text: String, + confirmButtonText: String = stringResource(R.string.action_save), + showDismissButton: Boolean = true, + dismissButtonText: String = stringResource(R.string.action_cancel), + properties: DialogProperties = DialogProperties(), + onSave: (String) -> Unit, + onDismiss: () -> Unit +) { + val textFieldState = rememberTextFieldState() + var isError by rememberSaveable { mutableStateOf(false) } + + val supportingText = listOf( + stringResource(R.string.tip_max_length_5), + stringResource(R.string.error_no_content_entered), + stringResource(R.string.error_exceeds_5_chars) + ) + + var currentSupportingText by remember { mutableStateOf(supportingText[0]) } + + BasicDialog( + visible = visible, + icon = icon, + title = title, + text = { + // 已经是实现好滚动的Column布局 + Text(text) + Spacer(Modifier.size(3.dp)) + OutlinedTextField( + state = textFieldState, + lineLimits = TextFieldLineLimits.SingleLine, + label = { Text(stringResource(R.string.label_enter_sth)) }, + supportingText = { AnimatedContent(targetState = currentSupportingText) { Text(it) } }, + isError = isError + ) + }, + confirmButton = confirmButtonText, + dismissButton = if (showDismissButton) dismissButtonText else null, + onConfirm = { + val trimmedText = textFieldState.text.trim() + when { + trimmedText.isEmpty() -> { + isError = true + currentSupportingText = supportingText[1] + return@BasicDialog + } + + trimmedText.length > 5 -> { + isError = true + currentSupportingText = supportingText[2] + return@BasicDialog + } + + else -> { + onSave(trimmedText.toString()) + isError = false + currentSupportingText = supportingText[0] + textFieldState.clearText() + onDismiss() + } + } + }, + onDismiss = onDismiss, + properties = properties, + modifier = modifier + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt new file mode 100644 index 0000000..1367653 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt @@ -0,0 +1,66 @@ +package cn.super12138.todo.ui.pages.settings.components.category + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import cn.super12138.todo.R +import cn.super12138.todo.ui.TodoDefaults +import cn.super12138.todo.utils.VibrationUtils + +@Composable +fun CategoryItem( + modifier: Modifier = Modifier, + name: String, + onDelete: (String) -> Unit = {} +) { + val view = LocalView.current + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .clip(MaterialTheme.shapes.large) + .padding( + horizontal = TodoDefaults.settingsItemHorizontalPadding, + vertical = TodoDefaults.settingsItemVerticalPadding + ), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onSurface, + fontSize = 20.sp + ), + modifier = Modifier.weight(1f) + ) + + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onDelete(name) + } + ) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(R.string.action_delete) + ) + } + } +} \ No newline at end of file From 88d4132c9991e9a2fe2572200cfa82ddea93abde Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:54:48 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E5=88=97=E8=A1=A8=E9=A1=B9=E5=92=8C=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=98=BE=E7=A4=BA=E6=95=88?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化语言表述 优化添加重复分类时的处理方案 --- .../todo/ui/pages/editor/TodoEditorPage.kt | 2 +- .../editor/components/TodoCategoryChip.kt | 45 ++++++++++--------- .../todo/ui/pages/editor/state/EditorState.kt | 2 +- .../ui/pages/settings/SettingsDataCategory.kt | 37 +++++++++++---- .../components/category/CategoryItem.kt | 6 +-- app/src/main/res/values-zh-rCN/strings.xml | 18 ++++---- app/src/main/res/values/strings.xml | 4 +- 7 files changed, 69 insertions(+), 45 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 43f06d7..2335ec3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -80,7 +80,7 @@ fun TodoEditorPage( ) } + ChipItem(id = -1, name = "自定义") - var defaultIndex by remember { mutableIntStateOf(0) } + var defaultIndex by remember { mutableIntStateOf(-1) } LaunchedEffect(originalCategories, toDo) { if (originalCategories.isEmpty()) return@LaunchedEffect if (toDo == null) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt index c4c5707..0fde6c0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoCategoryChip.kt @@ -1,15 +1,14 @@ package cn.super12138.todo.ui.pages.editor.components -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import cn.super12138.todo.R import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.FilterChipGroup @@ -21,21 +20,23 @@ fun TodoCategoryChip( isLoading: Boolean = false, onCategorySelected: (Int) -> Unit ) { - Box(modifier = modifier.fillMaxWidth()) { - AnimatedContent( - targetState = isLoading, - transitionSpec = { fadeIn(tween(100)) togetherWith fadeOut(tween(100)) } - ) { - if (it) { - CircularProgressIndicator() - } else { - FilterChipGroup( - modifier = Modifier, - items = items, - defaultSelectedItemIndex = defaultSelectedItemIndex, - onSelectedChanged = onCategorySelected - ) - } + Column(modifier = modifier.fillMaxWidth()) { + if (isLoading) { + Text( + text = stringResource(R.string.tip_no_category_chip), + style = MaterialTheme.typography.labelLarge.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant + ), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) } + + FilterChipGroup( + modifier = Modifier, + items = items, + defaultSelectedItemIndex = defaultSelectedItemIndex, + onSelectedChanged = onCategorySelected + ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt index 46795c6..c8c54a5 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt @@ -13,7 +13,7 @@ import cn.super12138.todo.logic.database.TodoEntity class EditorState(val initialTodo: TodoEntity? = null) { var toDoContent by mutableStateOf(initialTodo?.content ?: "") var isErrorContent by mutableStateOf(false) - var selectedCategoryIndex by mutableIntStateOf(0) + var selectedCategoryIndex by mutableIntStateOf(-1) var categoryContent by mutableStateOf(initialTodo?.category ?: "") var isErrorCategory by mutableStateOf(false) var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 58e7734..3c05589 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -1,6 +1,11 @@ package cn.super12138.todo.ui.pages.settings +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.VisibilityThreshold +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -8,6 +13,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -23,8 +29,9 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.IntOffset import cn.super12138.todo.R import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton @@ -44,7 +51,6 @@ fun SettingsDataCategory( val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() val listState = rememberLazyListState() - val context = LocalContext.current var showDialog by rememberSaveable { mutableStateOf(false) } val categories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList()) @@ -74,18 +80,28 @@ fun SettingsDataCategory( ) { if (categories.isEmpty()) { item { - Text("空") + Text( + text = stringResource(R.string.tip_no_category_page), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) } } else { items(items = categories, key = { it }) { CategoryItem( name = it, onDelete = { it -> - scope.launch { - DataStoreManager.setCategories(categories - it) - } + scope.launch { DataStoreManager.setCategories(categories - it) } }, - modifier = Modifier.animateItem() + modifier = Modifier.animateItem( + fadeInSpec = tween(100), + placementSpec = spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = IntOffset.VisibilityThreshold + ), + fadeOutSpec = tween(100) + ) ) } } @@ -102,9 +118,12 @@ fun SettingsDataCategory( } } else { scope.launch { - snackbarHostState.showSnackbar( + /*snackbarHostState.showSnackbar( message = context.getString(R.string.error_category_duplicate) - ) + )*/ + // 调换分类位置 + val tempList = categories - it + DataStoreManager.setCategories(tempList + it) } } }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt index 1367653..e300df6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/category/CategoryItem.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components.category +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -36,7 +37,7 @@ fun CategoryItem( .clip(MaterialTheme.shapes.large) .padding( horizontal = TodoDefaults.settingsItemHorizontalPadding, - vertical = TodoDefaults.settingsItemVerticalPadding + vertical = TodoDefaults.settingsItemVerticalPadding / 2 ), verticalAlignment = Alignment.CenterVertically ) { @@ -44,9 +45,8 @@ fun CategoryItem( text = name, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( + style = MaterialTheme.typography.bodyLarge.copy( color = MaterialTheme.colorScheme.onSurface, - fontSize = 20.sp ), modifier = Modifier.weight(1f) ) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8b088d0..04fba0a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -29,7 +29,7 @@ 开发者 开放源代码许可 查看应用使用的开源库及其许可 - 类别 + 分类 不紧急 不重要 默认 @@ -72,7 +72,7 @@ 查看源代码、提交错误报告和改进建议 退出编辑后将无法找回你修改过的数据。确定退出编辑吗? 添加先后顺序 - 类别 + 分类 优先级 完成状态 首字母(升序) @@ -96,16 +96,18 @@ 安全模式 阻止截屏并保护后台预览图 更多 - 输入类别名称 + 输入分类名称 当前共有 %1$d 项任务,其中 %2$d 项已完成,%3$d 项未完成 当前没有任务 - 类别管理 + 分类管理 数据管理 - 管理任务的类别标签 - 添加类别 + 管理任务的分类标签 + 添加分类 输入内容 不超过 5 个字 超过 5 个字 - 输入你想添加的类别 - 该类别已经存在 + 输入你想添加的分类名称 + + 当前暂无自定义分类,你可以在设置中添加分类 + 当前暂无自定义分类 \ 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 a9b4560..67dcd2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,5 +108,7 @@ Up to 5 characters Exceeds 5 characters Enter the category you want to add - The category is duplicate + + There are currently no custom categories. You can add categories in the settings. + There are no custom categories at the moment. \ No newline at end of file From 34b316120a993bbeb6db27dbc3d47c72a2a548cd Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:01:08 +0800 Subject: [PATCH 4/8] =?UTF-8?q?perf:=20=E9=87=8D=E7=BB=84=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ui/pages/editor/TodoEditorPage.kt | 38 ++++++++--------- .../super12138/todo/ui/pages/main/MainPage.kt | 15 ++++--- .../todo/ui/pages/settings/SettingsData.kt | 21 +++++----- .../ui/pages/settings/SettingsDataCategory.kt | 42 +++++++++---------- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 2335ec3..d94901a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -246,24 +246,24 @@ fun TodoEditorPage( } } } + + ConfirmDialog( + visible = uiState.showExitConfirmDialog, + icon = Icons.AutoMirrored.Outlined.Undo, + text = stringResource(R.string.tip_discard_changes), + onConfirm = { + uiState.showExitConfirmDialog = false + onNavigateUp() + }, + onDismiss = { uiState.showExitConfirmDialog = false } + ) + + ConfirmDialog( + visible = uiState.showDeleteConfirmDialog, + icon = Icons.Outlined.Delete, + text = stringResource(R.string.tip_delete_task, 1), + onConfirm = onDelete, + onDismiss = { uiState.showDeleteConfirmDialog = false } + ) } - - ConfirmDialog( - visible = uiState.showExitConfirmDialog, - icon = Icons.AutoMirrored.Outlined.Undo, - text = stringResource(R.string.tip_discard_changes), - onConfirm = { - uiState.showExitConfirmDialog = false - onNavigateUp() - }, - onDismiss = { uiState.showExitConfirmDialog = false } - ) - - ConfirmDialog( - visible = uiState.showDeleteConfirmDialog, - icon = Icons.Outlined.Delete, - text = stringResource(R.string.tip_delete_task, 1), - onConfirm = onDelete, - onDismiss = { uiState.showDeleteConfirmDialog = false } - ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index bbda843..859b7bf 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -211,13 +211,12 @@ fun MainPage( ) } } + ConfirmDialog( + visible = showDeleteConfirmDialog, + icon = Icons.Outlined.Delete, + text = stringResource(R.string.tip_delete_task, selectedTodoIds.size), + onConfirm = { viewModel.deleteSelectedTodo() }, + onDismiss = { showDeleteConfirmDialog = false } + ) } - - ConfirmDialog( - visible = showDeleteConfirmDialog, - icon = Icons.Outlined.Delete, - text = stringResource(R.string.tip_delete_task, selectedTodoIds.size), - onConfirm = { viewModel.deleteSelectedTodo() }, - onDismiss = { showDeleteConfirmDialog = false } - ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt index dc86d23..b98852a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsData.kt @@ -147,18 +147,17 @@ fun SettingsData( ) } } + ConfirmDialog( + visible = showRestoreDialog, + icon = Icons.Outlined.RestartAlt, + title = stringResource(R.string.tip_tips), + text = stringResource(R.string.tip_restore_success), + showDismissButton = false, + onConfirm = { restartApp(context) }, + onDismiss = { showRestoreDialog = false }, + properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) + ) } - - ConfirmDialog( - visible = showRestoreDialog, - icon = Icons.Outlined.RestartAlt, - title = stringResource(R.string.tip_tips), - text = stringResource(R.string.tip_restore_success), - showDismissButton = false, - onConfirm = { restartApp(context) }, - onDismiss = { showRestoreDialog = false }, - properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) - ) } /** diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt index 3c05589..fa2661d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsDataCategory.kt @@ -106,27 +106,27 @@ fun SettingsDataCategory( } } } - } - CategoryPromptDialog( - visible = showDialog, - text = stringResource(R.string.tip_enter_category), - onSave = { - if (!categories.contains(it)) { - scope.launch { - DataStoreManager.setCategories(categories + it) + CategoryPromptDialog( + visible = showDialog, + text = stringResource(R.string.tip_enter_category), + onSave = { + if (!categories.contains(it)) { + scope.launch { + DataStoreManager.setCategories(categories + it) + } + } else { + scope.launch { + /*snackbarHostState.showSnackbar( + message = context.getString(R.string.error_category_duplicate) + )*/ + // 调换分类位置 + val tempList = categories - it + DataStoreManager.setCategories(tempList + it) + } } - } else { - scope.launch { - /*snackbarHostState.showSnackbar( - message = context.getString(R.string.error_category_duplicate) - )*/ - // 调换分类位置 - val tempList = categories - it - DataStoreManager.setCategories(tempList + it) - } - } - }, - onDismiss = { showDialog = false } - ) + }, + onDismiss = { showDialog = false } + ) + } } \ No newline at end of file From e8ac66c5ae46c38cf9d8f9dadf00d4b6c1efac2b Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:01:28 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=E7=BC=96=E8=BE=91=E5=99=A8=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E6=96=87=E6=9C=AC=E5=8A=A8=E7=94=BB=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/components/TodoEditorTextFields.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt index 68ea089..fa4e520 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt @@ -1,6 +1,10 @@ package cn.super12138.todo.ui.pages.editor.components import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -21,7 +25,11 @@ fun TodoContentTextField( label = { Text(stringResource(R.string.placeholder_add_todo)) }, isError = isError, supportingText = { - AnimatedVisibility(isError) { + AnimatedVisibility( + visible = isError, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { Text(stringResource(R.string.error_no_content_entered)) } }, @@ -42,7 +50,11 @@ fun TodoCategoryTextField( label = { Text(stringResource(R.string.label_enter_category_name)) }, isError = isError, supportingText = { - AnimatedVisibility(isError) { + AnimatedVisibility( + visible = isError, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { Text(stringResource(R.string.error_no_content_entered)) } }, From 199bb8bbff963face0046e0b22de422141b8c706 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:48:32 +0800 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E9=BB=98=E8=AE=A4=E5=80=BC=E5=B1=95=E7=A4=BA=20&=20?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E8=B5=84=E6=BA=90=20&=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../cn/super12138/todo/constants/Constants.kt | 4 +- .../todo/logic/database/TodoDatabase.kt | 4 +- .../todo/ui/pages/editor/TodoEditorPage.kt | 44 ++++++++++++------- .../super12138/todo/ui/pages/main/MainPage.kt | 8 ++-- .../todo/ui/pages/main/ManagerFragment.kt | 12 +++-- .../todo/ui/pages/main/components/TodoCard.kt | 27 +++++------- app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 9 files changed, 57 insertions(+), 48 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f648af..b7c1227 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,7 +35,7 @@ android { applicationId = "cn.super12138.todo" minSdk = 24 targetSdk = 36 - versionCode = 742 + versionCode = 748 versionName = "2.1.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 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 616a5d0..8745294 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -5,8 +5,8 @@ object Constants { const val GITHUB_REPO = "https://github.com/Super12138/ToDo/" const val KEY_TODO_FAB_TRANSITION = "todo_fab" - const val KEY_TODO_CONTENT_TRANSITION = "todo_content" - const val KEY_TODO_CATEGORY_TRANSITION = "todo_category" + // const val KEY_TODO_CONTENT_TRANSITION = "todo_content" + // const val KEY_TODO_CATEGORY_TRANSITION = "todo_category" const val DB_NAME = "todo" const val DB_TABLE_NAME = "todo" diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt index 3dfcdc7..d6569d9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoDatabase.kt @@ -41,9 +41,9 @@ abstract class TodoDatabase : RoomDatabase() { private val MIGRATION_3_4 = object : Migration(3, 4) { override fun migrate(db: SupportSQLiteDatabase) { // 创建一个新表,其中不含有subject,并且有一个新的category字段(由custom_subject迁移而来) - db.execSQL("CREATE TABLE IF NOT EXISTS todo_new (content TEXT NOT NULL, category TEXT NOT NULL DEFAULT 'Default Value', completed INTEGER NOT NULL, priority REAL NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") + db.execSQL("CREATE TABLE IF NOT EXISTS todo_new (content TEXT NOT NULL, category TEXT NOT NULL DEFAULT '', completed INTEGER NOT NULL, priority REAL NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") // 将旧表中的数据迁移到新表中 - db.execSQL("INSERT INTO todo_new (content, category, completed, priority, id) SELECT content, COALESCE(NULLIF(custom_subject, ''), 'Default Value') AS category, completed, priority, id FROM todo") + db.execSQL("INSERT INTO todo_new (content, category, completed, priority, id) SELECT content, COALESCE(NULLIF(custom_subject, ''), '') AS category, completed, priority, id FROM todo") // 删除旧表 db.execSQL("DROP TABLE todo") // 重命名新表 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index d94901a..1747f01 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -78,7 +78,7 @@ fun TodoEditorPage( id = index, name = category ) - } + ChipItem(id = -1, name = "自定义") + } + ChipItem(id = -1, name = stringResource(R.string.label_customization)) var defaultIndex by remember { mutableIntStateOf(-1) } LaunchedEffect(originalCategories, toDo) { @@ -167,14 +167,19 @@ fun TodoEditorPage( .fillMaxSize() ) { item { - with(sharedTransitionScope) { - TodoContentTextField( - value = uiState.toDoContent, - onValueChange = { uiState.toDoContent = it }, - isError = uiState.isErrorContent, - modifier = Modifier.fillMaxWidth() - ) - } + // with(sharedTransitionScope) { + TodoContentTextField( + value = uiState.toDoContent, + onValueChange = { uiState.toDoContent = it }, + isError = uiState.isErrorContent, + modifier = Modifier + .fillMaxWidth() + /*.sharedBounds( + sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"), + animatedVisibilityScope = animatedVisibilityScope + )*/ + ) + // } } item { @@ -196,14 +201,19 @@ fun TodoEditorPage( enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { - with(sharedTransitionScope) { - TodoCategoryTextField( - value = uiState.categoryContent, - onValueChange = { uiState.categoryContent = it }, - isError = uiState.isErrorCategory, - modifier = Modifier.fillMaxWidth() - ) - } + // with(sharedTransitionScope) { + TodoCategoryTextField( + value = uiState.categoryContent, + onValueChange = { uiState.categoryContent = it }, + isError = uiState.isErrorCategory, + modifier = Modifier + .fillMaxWidth() + /*.sharedBounds( + sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_${toDo?.id}"), + animatedVisibilityScope = animatedVisibilityScope + )*/ + ) + // } } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 859b7bf..92fb5bd 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -160,8 +160,8 @@ fun MainPage( } }, selectedTodoIds = selectedTodoIds, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, + // sharedTransitionScope = sharedTransitionScope, + // animatedVisibilityScope = animatedVisibilityScope, modifier = Modifier .weight(3f) .fillMaxSize() @@ -203,8 +203,8 @@ fun MainPage( } }, selectedTodoIds = selectedTodoIds, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, + // sharedTransitionScope = sharedTransitionScope, + // animatedVisibilityScope = animatedVisibilityScope, modifier = Modifier .weight(3f) .fillMaxSize() diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index 0c57b44..0449b24 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -1,8 +1,6 @@ package cn.super12138.todo.ui.pages.main -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -33,8 +31,8 @@ fun ManagerFragment( onItemLongClick: (TodoEntity) -> Unit = {}, onItemChecked: (TodoEntity) -> Unit = {}, selectedTodoIds: List, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope + // sharedTransitionScope: SharedTransitionScope, + // animatedVisibilityScope: AnimatedVisibilityScope ) { LazyColumnCustomScrollBar( state = state, @@ -64,7 +62,7 @@ fun ManagerFragment( key = { it.id } ) { item -> TodoCard( - id = item.id, + // id = item.id, content = item.content, category = item.category, completed = item.isCompleted, @@ -73,8 +71,8 @@ fun ManagerFragment( onCardClick = { onItemClick(item) }, onCardLongClick = { onItemLongClick(item) }, onChecked = { onItemChecked(item) }, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, + // sharedTransitionScope = sharedTransitionScope, + // animatedVisibilityScope = animatedVisibilityScope, modifier = Modifier .padding(vertical = 5.dp) .animateItem() // TODO: 设置动画时间 diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index 2c35b61..079ed00 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -1,9 +1,7 @@ package cn.super12138.todo.ui.pages.main.components import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee @@ -38,7 +36,6 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import cn.super12138.todo.R -import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.utils.VibrationUtils @@ -47,7 +44,7 @@ import cn.super12138.todo.utils.VibrationUtils @Composable fun TodoCard( modifier: Modifier = Modifier, - id: Int, + // id: Int, content: String, category: String, completed: Boolean, @@ -56,8 +53,8 @@ fun TodoCard( onCardClick: () -> Unit = {}, onCardLongClick: () -> Unit = {}, onChecked: () -> Unit = {}, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope + // sharedTransitionScope: SharedTransitionScope, + // animatedVisibilityScope: AnimatedVisibilityScope ) { val view = LocalView.current val context = LocalContext.current @@ -122,7 +119,7 @@ fun TodoCard( } } ) { - with(sharedTransitionScope) { + // with(sharedTransitionScope) { Text( text = content, style = MaterialTheme.typography.titleLarge, @@ -130,27 +127,27 @@ fun TodoCard( overflow = TextOverflow.Ellipsis, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, modifier = Modifier - .sharedBounds( + /*.sharedBounds( sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_$id"), animatedVisibilityScope = animatedVisibilityScope - ) + )*/ .basicMarquee() // TODO: 后续评估性能影响 ) - } + // } } - with(sharedTransitionScope) { + // with(sharedTransitionScope) { Text( - text = category, + text = category.ifEmpty { stringResource(R.string.tip_default_category) }, style = MaterialTheme.typography.labelMedium, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, maxLines = 1, - modifier = Modifier.sharedBounds( + /*modifier = Modifier.sharedBounds( sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"), animatedVisibilityScope = animatedVisibilityScope - ) + )*/ ) - } + // } } AnimatedVisibility(!selected && !completed) { diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 04fba0a..3fa491f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -110,4 +110,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 67dcd2b..1806a66 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,4 +111,6 @@ There are currently no custom categories. You can add categories in the settings. There are no custom categories at the moment. + Default Category, please modify + Customization \ No newline at end of file From 0f5532de8adf6862bd28bbc1c2fca28be21a186c Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:51:43 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=E5=8F=96=E6=B6=88=E5=9C=A8=E6=9C=89?= =?UTF-8?q?=E9=A2=84=E7=BD=AE=E5=88=86=E7=B1=BB=E6=97=B6=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E6=96=87=E6=9C=AC=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 1747f01..c77b033 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -91,7 +91,6 @@ fun TodoEditorPage( val index = categories.firstOrNull { it.name == toDo.category }?.id ?: -1 defaultIndex = index uiState.selectedCategoryIndex = index - if (index != -1) uiState.categoryContent = "" } } From 4db78310f94e2be934e7c6a70fd8e116d29173a6 Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:39:34 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=E6=BC=8F=E5=8A=A0=E7=9A=84=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ui/pages/editor/TodoEditorPage.kt | 9 +++++++++ .../pages/editor/components/TodoEditorTextFields.kt | 12 +++--------- .../todo/ui/pages/editor/state/EditorState.kt | 4 +++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index c77b033..eb2db7f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -205,6 +205,15 @@ fun TodoEditorPage( value = uiState.categoryContent, onValueChange = { uiState.categoryContent = it }, isError = uiState.isErrorCategory, + supportingText = when { + uiState.categoryContent.trim().isEmpty() -> + stringResource(R.string.error_no_content_entered) + + uiState.categoryContent.length > 5 -> + stringResource(R.string.error_exceeds_5_chars) + + else -> stringResource(R.string.tip_max_length_5) + }, modifier = Modifier .fillMaxWidth() /*.sharedBounds( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt index fa4e520..457a096 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/components/TodoEditorTextFields.kt @@ -42,6 +42,7 @@ fun TodoCategoryTextField( value: String, onValueChange: (String) -> Unit, isError: Boolean, + supportingText: String = stringResource(R.string.tip_max_length_5), modifier: Modifier = Modifier ) { TextField( @@ -49,15 +50,8 @@ fun TodoCategoryTextField( onValueChange = onValueChange, label = { Text(stringResource(R.string.label_enter_category_name)) }, isError = isError, - supportingText = { - AnimatedVisibility( - visible = isError, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically() - ) { - Text(stringResource(R.string.error_no_content_entered)) - } - }, + supportingText = { Text(supportingText) }, + maxLines = 1, modifier = modifier ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt index c8c54a5..308a988 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt @@ -29,7 +29,9 @@ class EditorState(val initialTodo: TodoEntity? = null) { */ fun setErrorIfNotValid(): Boolean { isErrorContent = toDoContent.trim().isEmpty() - isErrorCategory = if (selectedCategoryIndex == -1) categoryContent.trim().isEmpty() else false + isErrorCategory = if (selectedCategoryIndex == -1) { + categoryContent.trim().isEmpty() || categoryContent.trim().length > 5 + } else false return isErrorContent || isErrorCategory }