This commit is contained in:
Super12138 2025-07-03 20:51:49 +08:00
parent 0748a16547
commit 29d71fc060
25 changed files with 421 additions and 174 deletions

View file

@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.aboutlibraries) alias(libs.plugins.aboutlibraries)
} }
@ -34,7 +35,7 @@ android {
applicationId = "cn.super12138.todo" applicationId = "cn.super12138.todo"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 735 versionCode = 742
versionName = "2.1.2" versionName = "2.1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -108,9 +109,10 @@ dependencies {
implementation(libs.nl.dionsegijn.konfetti.compose) implementation(libs.nl.dionsegijn.konfetti.compose)
// Lazy Column Scrollbar // Lazy Column Scrollbar
implementation(libs.lazycolumnscrollbar) implementation(libs.lazycolumnscrollbar)
// Kotlin Coroutines // Kotlin
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)
// Room // Room
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.ktx)

View file

@ -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')"
]
}
}

View file

@ -6,7 +6,7 @@ object Constants {
const val KEY_TODO_FAB_TRANSITION = "todo_fab" const val KEY_TODO_FAB_TRANSITION = "todo_fab"
const val KEY_TODO_CONTENT_TRANSITION = "todo_content" 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_NAME = "todo"
const val DB_TABLE_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 = "haptic_feedback"
const val PREF_HAPTIC_FEEDBACK_DEFAULT = true const val PREF_HAPTIC_FEEDBACK_DEFAULT = true
const val PREF_CATEGORIES = "categories"
const val PREF_CATEGORIES_DEFAULT = "[]"
} }

View file

@ -8,7 +8,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
@Database(entities = [TodoEntity::class], version = 3) @Database(entities = [TodoEntity::class], version = 4)
abstract class TodoDatabase : RoomDatabase() { abstract class TodoDatabase : RoomDatabase() {
abstract fun toDoDao(): TodoDao abstract fun toDoDao(): TodoDao
@ -22,7 +22,7 @@ abstract class TodoDatabase : RoomDatabase() {
TodoDatabase::class.java, TodoDatabase::class.java,
Constants.DB_NAME Constants.DB_NAME
) )
.addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_2_3, MIGRATION_3_4)
.fallbackToDestructiveMigration(false) .fallbackToDestructiveMigration(false)
.build() .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 ''") 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")
}
}
} }
} }

View file

@ -8,8 +8,7 @@ import cn.super12138.todo.constants.Constants
@Entity(tableName = Constants.DB_TABLE_NAME) @Entity(tableName = Constants.DB_TABLE_NAME)
data class TodoEntity( data class TodoEntity(
@ColumnInfo(name = "content") val content: String, @ColumnInfo(name = "content") val content: String,
@ColumnInfo(name = "subject") val subject: Int, @ColumnInfo(name = "category") val category: String = "",
@ColumnInfo(name = "custom_subject") val customSubject: String = "",
@ColumnInfo(name = "completed") val isCompleted: Boolean = false, @ColumnInfo(name = "completed") val isCompleted: Boolean = false,
@ColumnInfo(name = "priority") val priority: Float, @ColumnInfo(name = "priority") val priority: Float,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0, @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0,

View file

@ -6,11 +6,13 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import cn.super12138.todo.TodoApp import cn.super12138.todo.TodoApp
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.json.Json
object DataStoreManager { object DataStoreManager {
private val Context.dataStore by preferencesDataStore( private val Context.dataStore by preferencesDataStore(
@ -40,6 +42,9 @@ object DataStoreManager {
private val SECURE_MODE = booleanPreferencesKey(Constants.PREF_SECURE_MODE) private val SECURE_MODE = booleanPreferencesKey(Constants.PREF_SECURE_MODE)
private val HAPTIC_FEEDBACK = booleanPreferencesKey(Constants.PREF_HAPTIC_FEEDBACK) private val HAPTIC_FEEDBACK = booleanPreferencesKey(Constants.PREF_HAPTIC_FEEDBACK)
// 数据
private val CATEGORIES = stringPreferencesKey(Constants.PREF_CATEGORIES)
// Getters // Getters
val dynamicColorFlow: Flow<Boolean> = dataStore.data.map { preferences -> val dynamicColorFlow: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[DYNAMIC_COLOR] ?: Constants.PREF_DYNAMIC_COLOR_DEFAULT preferences[DYNAMIC_COLOR] ?: Constants.PREF_DYNAMIC_COLOR_DEFAULT
@ -73,6 +78,10 @@ object DataStoreManager {
preferences[HAPTIC_FEEDBACK] ?: Constants.PREF_HAPTIC_FEEDBACK_DEFAULT preferences[HAPTIC_FEEDBACK] ?: Constants.PREF_HAPTIC_FEEDBACK_DEFAULT
} }
val categoriesFlow: Flow<List<String>> = dataStore.data.map { preferences ->
Json.decodeFromString(preferences[CATEGORIES] ?: Constants.PREF_CATEGORIES_DEFAULT)
}
// Setters // Setters
suspend fun setDynamicColor(value: Boolean) { suspend fun setDynamicColor(value: Boolean) {
dataStore.edit { preferences -> dataStore.edit { preferences ->
@ -121,4 +130,10 @@ object DataStoreManager {
preferences[HAPTIC_FEEDBACK] = value preferences[HAPTIC_FEEDBACK] = value
} }
} }
suspend fun setCategories(value: List<String>) {
dataStore.edit { preferences ->
preferences[CATEGORIES] = Json.encodeToString(value)
}
}
} }

View file

@ -8,7 +8,7 @@ enum class SortingMethod(val id: Int) {
Sequential(1), Sequential(1),
// 按学科 // 按学科
Subject(2), Category(2),
// 按优先级 // 按优先级
Priority(3), Priority(3),
@ -25,7 +25,7 @@ enum class SortingMethod(val id: Int) {
fun getDisplayName(context: Context): String { fun getDisplayName(context: Context): String {
val resId = when (this) { val resId = when (this) {
Sequential -> R.string.sorting_sequential Sequential -> R.string.sorting_sequential
Subject -> R.string.sorting_subject Category -> R.string.sorting_category
Priority -> R.string.sorting_priority Priority -> R.string.sorting_priority
Completion -> R.string.sorting_completion Completion -> R.string.sorting_completion
AlphabeticalAscending -> R.string.sorting_alphabetical_ascending AlphabeticalAscending -> R.string.sorting_alphabetical_ascending

View file

@ -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
}
}

View file

@ -1,5 +1,6 @@
package cn.super12138.todo.ui.components package cn.super12138.todo.ui.components
import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -16,6 +17,8 @@ import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@ -27,6 +30,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
import kotlin.math.log
/** /**
* 部分参考https://github.com/Rhythamtech/FilterChipGroup-Compose-Android/blob/main/FilterChipGroup.kt * 部分参考https://github.com/Rhythamtech/FilterChipGroup-Compose-Android/blob/main/FilterChipGroup.kt
@ -39,16 +43,23 @@ fun FilterChipGroup(
defaultSelectedItemIndex: Int = 0, defaultSelectedItemIndex: Int = 0,
onSelectedChanged: (Int) -> Unit = {} onSelectedChanged: (Int) -> Unit = {}
) { ) {
SideEffect {
Log.d("TAG", "来自 FilterChipGroup重组啦")
}
val view = LocalView.current val view = LocalView.current
var selectedItemId by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) } var selectedItemIndex by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) }
LaunchedEffect(defaultSelectedItemIndex) {
selectedItemIndex = defaultSelectedItemIndex
}
FlowRow(modifier = modifier) { FlowRow(modifier = modifier) {
items.forEach { item -> items.forEach { item ->
FilterChipItem( FilterChipItem(
selected = item.id == selectedItemId, selected = item.id == selectedItemIndex,
text = item.text, text = item.name,
onClick = { onClick = {
selectedItemId = item.id selectedItemIndex = item.id
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
onSelectedChanged(item.id) onSelectedChanged(item.id)
} }
@ -87,5 +98,5 @@ private fun FilterChipItem(
data class ChipItem( data class ChipItem(
val id: Int, val id: Int,
val text: String val name: String
) )

View file

@ -2,15 +2,24 @@ package cn.super12138.todo.ui.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState 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.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
@ -19,6 +28,66 @@ import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.utils.VibrationUtils 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 @Composable
fun ConfirmDialog( fun ConfirmDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,

View file

@ -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.SettingsAboutLicence
import cn.super12138.todo.ui.pages.settings.SettingsAppearance import cn.super12138.todo.ui.pages.settings.SettingsAppearance
import cn.super12138.todo.ui.pages.settings.SettingsData 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.SettingsInterface
import cn.super12138.todo.ui.pages.settings.SettingsMain import cn.super12138.todo.ui.pages.settings.SettingsMain
import cn.super12138.todo.ui.theme.materialSharedAxisXIn import cn.super12138.todo.ui.theme.materialSharedAxisXIn
@ -111,10 +112,15 @@ fun TodoNavigation(
composable(TodoScreen.SettingsData.name) { composable(TodoScreen.SettingsData.name) {
SettingsData( SettingsData(
viewModel = viewModel, viewModel = viewModel,
toCategoryManager = {navController.navigate(TodoScreen.SettingsDataCategory.name)},
onNavigateUp = { navController.navigateUp() } onNavigateUp = { navController.navigateUp() }
) )
} }
composable(TodoScreen.SettingsDataCategory.name) {
SettingsDataCategory(onNavigateUp = {navController.navigateUp()})
}
composable(TodoScreen.SettingsAbout.name) { composable(TodoScreen.SettingsAbout.name) {
SettingsAbout( SettingsAbout(
//toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) }, //toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) },

View file

@ -7,6 +7,7 @@ enum class TodoScreen {
SettingsAppearance, SettingsAppearance,
SettingsInterface, SettingsInterface,
SettingsData, SettingsData,
SettingsDataCategory,
SettingsAbout, SettingsAbout,
//SettingsAboutSpecial, //SettingsAboutSpecial,
SettingsAboutLicence SettingsAboutLicence

View file

@ -1,5 +1,6 @@
package cn.super12138.todo.ui.pages.editor package cn.super12138.todo.ui.pages.editor
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.AnimatedVisibilityScope
@ -26,28 +27,32 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.database.TodoEntity 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.TodoDefaults
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.FilterChipGroup
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold 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.TodoContentTextField
import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider 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.ui.pages.editor.state.rememberEditorState
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
@ -63,11 +68,40 @@ fun TodoEditorPage(
animatedVisibilityScope: AnimatedVisibilityScope animatedVisibilityScope: AnimatedVisibilityScope
) { ) {
val view = LocalView.current val view = LocalView.current
val context = LocalContext.current
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val uiState = rememberEditorState(initialTodo = toDo) 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() { fun checkModifiedBeforeBack() {
if (uiState.isModified()) { if (uiState.isModified()) {
@ -77,9 +111,7 @@ fun TodoEditorPage(
} }
} }
BackHandler { BackHandler { checkModifiedBeforeBack() }
checkModifiedBeforeBack()
}
LargeTopAppBarScaffold( LargeTopAppBarScaffold(
title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task),
@ -106,7 +138,14 @@ fun TodoEditorPage(
return@AnimatedExtendedFloatingActionButton return@AnimatedExtendedFloatingActionButton
} else { } else {
uiState.clearError() 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 modifier = Modifier
@ -137,50 +176,34 @@ fun TodoEditorPage(
isError = uiState.isErrorContent, isError = uiState.isErrorContent,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
) )
} }
} }
item { item {
Text( Text(
text = stringResource(R.string.label_subject), text = stringResource(R.string.label_category),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
val subjects = remember {
Subjects.entries.map {
ChipItem(
id = it.id,
text = it.getDisplayName(context)
)
}
}
FilterChipGroup( FilterChipGroup(
items = subjects, items = categories,
defaultSelectedItemIndex = toDo?.subject ?: Subjects.Chinese.id, defaultSelectedItemIndex = defaultIndex,
onSelectedChanged = { uiState.selectedSubjectId = it }, onSelectedChanged = { uiState.selectedCategoryIndex = it },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
AnimatedVisibility( AnimatedVisibility(
visible = isCustomSubject, visible = isCustomCategory,
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically() exit = fadeOut() + shrinkVertically()
) { ) {
with(sharedTransitionScope) { with(sharedTransitionScope) {
TodoSubjectTextField( TodoCategoryTextField(
value = uiState.subjectContent, value = uiState.categoryContent,
onValueChange = { uiState.subjectContent = it }, onValueChange = { uiState.categoryContent = it },
isError = uiState.isErrorSubject, isError = uiState.isErrorCategory,
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_${toDo?.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
) )
} }
} }

View file

@ -30,7 +30,7 @@ fun TodoContentTextField(
} }
@Composable @Composable
fun TodoSubjectTextField( fun TodoCategoryTextField(
value: String, value: String,
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
isError: Boolean, isError: Boolean,
@ -39,7 +39,7 @@ fun TodoSubjectTextField(
TextField( TextField(
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
label = { Text(stringResource(R.string.label_enter_subject_name)) }, label = { Text(stringResource(R.string.label_enter_category_name)) },
isError = isError, isError = isError,
supportingText = { supportingText = {
AnimatedVisibility(isError) { AnimatedVisibility(isError) {

View file

@ -9,18 +9,16 @@ import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.model.Subjects
class EditorState( class EditorState(val initialTodo: TodoEntity? = null) {
val initialTodo: TodoEntity? = null,
) {
var toDoContent by mutableStateOf(initialTodo?.content ?: "") var toDoContent by mutableStateOf(initialTodo?.content ?: "")
var isErrorContent by mutableStateOf(false) var isErrorContent by mutableStateOf(false)
var selectedSubjectId by mutableIntStateOf(initialTodo?.subject ?: 0) var selectedCategoryIndex by mutableIntStateOf(0)
var subjectContent by mutableStateOf(initialTodo?.customSubject ?: "") var categoryContent by mutableStateOf(initialTodo?.category ?: "")
var isErrorSubject by mutableStateOf(false) var isErrorCategory by mutableStateOf(false)
var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f) var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f)
var isCompleted by mutableStateOf(initialTodo?.isCompleted == true) var isCompleted by mutableStateOf(initialTodo?.isCompleted == true)
var showExitConfirmDialog by mutableStateOf(false) var showExitConfirmDialog by mutableStateOf(false)
var showDeleteConfirmDialog by mutableStateOf(false) var showDeleteConfirmDialog by mutableStateOf(false)
@ -31,9 +29,8 @@ class EditorState(
*/ */
fun setErrorIfNotValid(): Boolean { fun setErrorIfNotValid(): Boolean {
isErrorContent = toDoContent.trim().isEmpty() isErrorContent = toDoContent.trim().isEmpty()
isErrorSubject = subjectContent.trim().isEmpty() && isErrorCategory = if (selectedCategoryIndex == -1) categoryContent.trim().isEmpty() else false
selectedSubjectId == Subjects.Custom.id return isErrorContent || isErrorCategory
return isErrorContent || isErrorSubject
} }
/** /**
@ -41,31 +38,16 @@ class EditorState(
*/ */
fun clearError() { fun clearError() {
isErrorContent = false 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 { fun isModified(): Boolean {
var isModified = false var isModified = false
if ((initialTodo?.content ?: "") != toDoContent) isModified = true if ((initialTodo?.content ?: "") != toDoContent) isModified = true
if ((initialTodo?.subject ?: 0) != selectedSubjectId) isModified = true if ((initialTodo?.category ?: "") != categoryContent) isModified = true
if ((initialTodo?.customSubject ?: "") != subjectContent) isModified = true
if ((initialTodo?.priority ?: 0f) != priorityState) isModified = true if ((initialTodo?.priority ?: 0f) != priorityState) isModified = true
if ((initialTodo?.isCompleted == true) != isCompleted) isModified = true if ((initialTodo?.isCompleted == true) != isCompleted) isModified = true
return isModified return isModified
@ -80,9 +62,9 @@ class EditorState(
value.initialTodo?.id ?: 0, value.initialTodo?.id ?: 0,
value.toDoContent, value.toDoContent,
value.isErrorContent, value.isErrorContent,
value.selectedSubjectId, value.selectedCategoryIndex,
value.subjectContent, value.categoryContent,
value.isErrorSubject, value.isErrorCategory,
value.priorityState, value.priorityState,
value.isCompleted, value.isCompleted,
value.showExitConfirmDialog, value.showExitConfirmDialog,
@ -96,9 +78,9 @@ class EditorState(
return EditorState(initialTodo).apply { return EditorState(initialTodo).apply {
toDoContent = list[1] as String toDoContent = list[1] as String
isErrorContent = list[2] as Boolean isErrorContent = list[2] as Boolean
selectedSubjectId = list[3] as Int selectedCategoryIndex = list[3] as Int
subjectContent = list[4] as String categoryContent = list[4] as String
isErrorSubject = list[5] as Boolean isErrorCategory = list[5] as Boolean
priorityState = list[6] as Float priorityState = list[6] as Float
isCompleted = list[7] as Boolean isCompleted = list[7] as Boolean
showExitConfirmDialog = list[8] as Boolean showExitConfirmDialog = list[8] as Boolean

View file

@ -150,7 +150,7 @@ fun MainPage(
viewModel.updateTodo( viewModel.updateTodo(
TodoEntity( TodoEntity(
content = content, content = content,
subject = subject, category = category,
isCompleted = true, isCompleted = true,
priority = priority, priority = priority,
id = id id = id
@ -193,7 +193,7 @@ fun MainPage(
viewModel.updateTodo( viewModel.updateTodo(
TodoEntity( TodoEntity(
content = content, content = content,
subject = subject, category = category,
isCompleted = true, isCompleted = true,
priority = priority, priority = priority,
id = id id = id

View file

@ -13,14 +13,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.model.Priority 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.TodoDefaults
import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar import cn.super12138.todo.ui.components.LazyColumnCustomScrollBar
import cn.super12138.todo.ui.pages.main.components.TodoCard import cn.super12138.todo.ui.pages.main.components.TodoCard
@ -38,8 +36,6 @@ fun ManagerFragment(
sharedTransitionScope: SharedTransitionScope, sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope animatedVisibilityScope: AnimatedVisibilityScope
) { ) {
val context = LocalContext.current
LazyColumnCustomScrollBar( LazyColumnCustomScrollBar(
state = state, state = state,
modifier = modifier modifier = modifier
@ -67,16 +63,10 @@ fun ManagerFragment(
items = list, items = list,
key = { it.id } key = { it.id }
) { item -> ) { item ->
val subject = if (item.subject == Subjects.Custom.id) {
item.customSubject
} else {
Subjects.fromId(item.subject).getDisplayName(context)
}
TodoCard( TodoCard(
id = item.id, id = item.id,
content = item.content, content = item.content,
subject = subject, category = item.category,
completed = item.isCompleted, completed = item.isCompleted,
priority = Priority.fromFloat(item.priority), priority = Priority.fromFloat(item.priority),
selected = selectedTodoIds.contains(item.id), selected = selectedTodoIds.contains(item.id),

View file

@ -49,7 +49,7 @@ fun TodoCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
id: Int, id: Int,
content: String, content: String,
subject: String, category: String,
completed: Boolean, completed: Boolean,
priority: Priority, priority: Priority,
selected: Boolean, selected: Boolean,
@ -141,12 +141,12 @@ fun TodoCard(
with(sharedTransitionScope) { with(sharedTransitionScope) {
Text( Text(
text = subject, text = category,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
maxLines = 1, maxLines = 1,
modifier = Modifier.sharedBounds( modifier = Modifier.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_$id"), sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_$id"),
animatedVisibilityScope = animatedVisibilityScope animatedVisibilityScope = animatedVisibilityScope
) )
) )

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons 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.FileDownload
import androidx.compose.material.icons.outlined.FileUpload import androidx.compose.material.icons.outlined.FileUpload
import androidx.compose.material.icons.outlined.RestartAlt 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.activities.MainActivity
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
import cn.super12138.todo.utils.SystemUtils import cn.super12138.todo.utils.SystemUtils
@ -41,6 +43,7 @@ import kotlin.system.exitProcess
@Composable @Composable
fun SettingsData( fun SettingsData(
viewModel: MainViewModel, viewModel: MainViewModel,
toCategoryManager: () -> Unit,
onNavigateUp: () -> Unit, onNavigateUp: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@ -114,6 +117,7 @@ fun SettingsData(
.padding(innerPadding) .padding(innerPadding)
) { ) {
item { item {
SettingsCategory(stringResource(R.string.pref_category_data_management))
SettingsItem( SettingsItem(
leadingIcon = Icons.Outlined.FileDownload, leadingIcon = Icons.Outlined.FileDownload,
title = stringResource(R.string.pref_backup), 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
)
}
} }
} }

View file

@ -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 }
)
}

View file

@ -41,7 +41,7 @@ class MainViewModel : ViewModel() {
toDos.map { list -> toDos.map { list ->
when (SortingMethod.fromId(sortingMethod)) { when (SortingMethod.fromId(sortingMethod)) {
SortingMethod.Sequential -> list.sortedBy { it.id } 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.Priority -> list.sortedByDescending { it.priority } // 优先级高的在前
SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前 SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前
SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content } SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content }

View file

@ -10,16 +10,6 @@
<string name="error_no_content_entered">没有输入内容</string> <string name="error_no_content_entered">没有输入内容</string>
<string name="tip_select_this">选择该项</string> <string name="tip_select_this">选择该项</string>
<string name="tip_mark_completed">标记为已完成</string> <string name="tip_mark_completed">标记为已完成</string>
<string name="subject_chinese">语文</string>
<string name="subject_math">数学</string>
<string name="subject_english">英语</string>
<string name="subject_biology">生物</string>
<string name="subject_geography">地理</string>
<string name="subject_physics">物理</string>
<string name="subject_moral">道法</string>
<string name="subject_chemistry">化学</string>
<string name="subject_history">历史</string>
<string name="subject_others">其它</string>
<string name="title_edit_task">修改待办</string> <string name="title_edit_task">修改待办</string>
<string name="tip_remain_tasks">剩余 %s 项任务</string> <string name="tip_remain_tasks">剩余 %s 项任务</string>
<string name="page_crash">应用程序出现错误</string> <string name="page_crash">应用程序出现错误</string>
@ -39,7 +29,7 @@
<string name="pref_developer">开发者</string> <string name="pref_developer">开发者</string>
<string name="pref_licence">开放源代码许可</string> <string name="pref_licence">开放源代码许可</string>
<string name="pref_licence_desc">查看应用使用的开源库及其许可</string> <string name="pref_licence_desc">查看应用使用的开源库及其许可</string>
<string name="label_subject">学科</string> <string name="label_category">类别</string>
<string name="priority_not_urgent">不紧急</string> <string name="priority_not_urgent">不紧急</string>
<string name="priority_not_important">不重要</string> <string name="priority_not_important">不重要</string>
<string name="priority_default">默认</string> <string name="priority_default">默认</string>
@ -82,7 +72,7 @@
<string name="pref_view_on_github_desc">查看源代码、提交错误报告和改进建议</string> <string name="pref_view_on_github_desc">查看源代码、提交错误报告和改进建议</string>
<string name="tip_discard_changes">退出编辑后将无法找回你修改过的数据。确定退出编辑吗?</string> <string name="tip_discard_changes">退出编辑后将无法找回你修改过的数据。确定退出编辑吗?</string>
<string name="sorting_sequential">添加先后顺序</string> <string name="sorting_sequential">添加先后顺序</string>
<string name="sorting_subject">学科</string> <string name="sorting_category">类别</string>
<string name="sorting_priority">优先级</string> <string name="sorting_priority">优先级</string>
<string name="sorting_completion">完成状态</string> <string name="sorting_completion">完成状态</string>
<string name="sorting_alphabetical_ascending">首字母(升序)</string> <string name="sorting_alphabetical_ascending">首字母(升序)</string>
@ -106,9 +96,16 @@
<string name="pref_secure_mode">安全模式</string> <string name="pref_secure_mode">安全模式</string>
<string name="pref_secure_mode_desc">阻止截屏并保护后台预览图</string> <string name="pref_secure_mode_desc">阻止截屏并保护后台预览图</string>
<string name="label_more">更多</string> <string name="label_more">更多</string>
<string name="happy_birthday">待办1岁生日快乐</string> <string name="label_enter_category_name">输入类别名称</string>
<string name="subject_customization">自定义</string>
<string name="label_enter_subject_name">输入学科名称</string>
<string name="accessibility_progress_tasks">当前共有 %1$d 项任务,其中 %2$d 项已完成,%3$d 项未完成</string> <string name="accessibility_progress_tasks">当前共有 %1$d 项任务,其中 %2$d 项已完成,%3$d 项未完成</string>
<string name="accessibility_progress_no_tasks">当前没有任务</string> <string name="accessibility_progress_no_tasks">当前没有任务</string>
<string name="pref_category_category_management">类别管理</string>
<string name="pref_category_data_management">数据管理</string>
<string name="pref_category_management_desc">管理任务的类别标签</string>
<string name="action_add_category">添加类别</string>
<string name="label_enter_sth">输入内容</string>
<string name="tip_max_length_5">不超过 5 个字</string>
<string name="error_exceeds_5_chars">超过 5 个字</string>
<string name="tip_enter_category">输入你想添加的类别</string>
<string name="error_category_duplicate">该类别已经存在</string>
</resources> </resources>

View file

@ -9,16 +9,6 @@
<string name="error_no_content_entered">No content entered</string> <string name="error_no_content_entered">No content entered</string>
<string name="tip_select_this">Select this</string> <string name="tip_select_this">Select this</string>
<string name="tip_mark_completed">Mark as completed</string> <string name="tip_mark_completed">Mark as completed</string>
<string name="subject_chinese">Chinese</string>
<string name="subject_math">Math</string>
<string name="subject_english">English</string>
<string name="subject_biology">Biology</string>
<string name="subject_geography">Geography</string>
<string name="subject_physics">Physics</string>
<string name="subject_moral">Morality and Rule of Law</string>
<string name="subject_chemistry">Chemistry</string>
<string name="subject_history">History</string>
<string name="subject_others">Others</string>
<string name="title_edit_task">Edit Task</string> <string name="title_edit_task">Edit Task</string>
<string name="tip_remain_tasks">%s tasks remaining</string> <string name="tip_remain_tasks">%s tasks remaining</string>
<string name="page_crash">Oops! App went wrong</string> <string name="page_crash">Oops! App went wrong</string>
@ -40,7 +30,7 @@
<string name="pref_developer">Developer</string> <string name="pref_developer">Developer</string>
<string name="pref_licence">Open Source Licences</string> <string name="pref_licence">Open Source Licences</string>
<string name="pref_licence_desc">Check the open source libraries used by the application and their licences.</string> <string name="pref_licence_desc">Check the open source libraries used by the application and their licences.</string>
<string name="label_subject">Subject</string> <string name="label_category">Category</string>
<string name="priority_not_urgent">Not Urgent</string> <string name="priority_not_urgent">Not Urgent</string>
<string name="priority_not_important">Not Important</string> <string name="priority_not_important">Not Important</string>
<string name="priority_default">Default</string> <string name="priority_default">Default</string>
@ -83,7 +73,7 @@
<string name="pref_view_on_github_desc">View source code, submit bug reports, and improvement suggestions</string> <string name="pref_view_on_github_desc">View source code, submit bug reports, and improvement suggestions</string>
<string name="tip_discard_changes">After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing?</string> <string name="tip_discard_changes">After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing?</string>
<string name="sorting_sequential">Sequential</string> <string name="sorting_sequential">Sequential</string>
<string name="sorting_subject">Subject</string> <string name="sorting_category">category</string>
<string name="sorting_priority">Priority</string> <string name="sorting_priority">Priority</string>
<string name="sorting_completion">Completion</string> <string name="sorting_completion">Completion</string>
<string name="sorting_alphabetical_ascending">Alphabetical (Ascending)</string> <string name="sorting_alphabetical_ascending">Alphabetical (Ascending)</string>
@ -107,9 +97,16 @@
<string name="pref_secure_mode">Secure Mode</string> <string name="pref_secure_mode">Secure Mode</string>
<string name="pref_secure_mode_desc">Prevent screenshots and protect the background preview image</string> <string name="pref_secure_mode_desc">Prevent screenshots and protect the background preview image</string>
<string name="label_more">More</string> <string name="label_more">More</string>
<string name="happy_birthday">Happy 1st birthday to ToDo</string> <string name="label_enter_category_name">Enter category name</string>
<string name="subject_customization">Customization</string>
<string name="label_enter_subject_name">Enter subject name</string>
<string name="accessibility_progress_tasks">Currently, there are %1$d tasks in total, with %2$d completed and %3$d unfinished.</string> <string name="accessibility_progress_tasks">Currently, there are %1$d tasks in total, with %2$d completed and %3$d unfinished.</string>
<string name="accessibility_progress_no_tasks">There are no tasks at the moment.</string> <string name="accessibility_progress_no_tasks">There are no tasks at the moment.</string>
<string name="pref_category_category_management">Category Management</string>
<string name="pref_category_data_management">Data Management</string>
<string name="pref_category_management_desc">Manage the category of tasks</string>
<string name="action_add_category">Add Category</string>
<string name="label_enter_sth">Enter something</string>
<string name="tip_max_length_5">Up to 5 characters</string>
<string name="error_exceeds_5_chars">Exceeds 5 characters</string>
<string name="tip_enter_category">Enter the category you want to add</string>
<string name="error_category_duplicate">The category is duplicate</string>
</resources> </resources>

View file

@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) 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.ksp) apply false
alias(libs.plugins.aboutlibraries) apply false alias(libs.plugins.aboutlibraries) apply false
} }

View file

@ -21,9 +21,10 @@ m3color = "2025.3"
# Konfetti # Konfetti
konfetti = "2.0.5" konfetti = "2.0.5"
# Lazy Column Scrollbar # Lazy Column Scrollbar
lazycolumnscrollbar = "2.2.0" lazyColumnScrollbar = "2.2.0"
# Kotlin # Kotlin
kotlinCoroutines = "1.10.2" kotlinCoroutines = "1.10.2"
kotlinSerialization = "1.9.0"
# Test # Test
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" 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" } nl-dionsegijn-konfetti-compose = { group = "nl.dionsegijn", name = "konfetti-compose", version.ref = "konfetti" }
# Lazy Column Scrollbar # Lazy Column Scrollbar
lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazycolumnscrollbar" } lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazyColumnScrollbar" }
# Kotlin # Kotlin
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } 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-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 # Test
junit = { group = "junit", name = "junit", version.ref = "junit" } 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" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", 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" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibsReleasePlugin" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibsReleasePlugin" }