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] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E5=AE=9E=E7=8E=B0=20#163=20#?= =?UTF-8?q?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