parent
0748a16547
commit
29d71fc060
25 changed files with 421 additions and 174 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "[]"
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Boolean> = 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<List<String>> = 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<String>) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[CATEGORIES] = Json.encodeToString(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ enum class TodoScreen {
|
|||
SettingsAppearance,
|
||||
SettingsInterface,
|
||||
SettingsData,
|
||||
SettingsDataCategory,
|
||||
SettingsAbout,
|
||||
//SettingsAboutSpecial,
|
||||
SettingsAboutLicence
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -10,16 +10,6 @@
|
|||
<string name="error_no_content_entered">没有输入内容</string>
|
||||
<string name="tip_select_this">选择该项</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="tip_remain_tasks">剩余 %s 项任务</string>
|
||||
<string name="page_crash">应用程序出现错误</string>
|
||||
|
|
@ -39,7 +29,7 @@
|
|||
<string name="pref_developer">开发者</string>
|
||||
<string name="pref_licence">开放源代码许可</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_important">不重要</string>
|
||||
<string name="priority_default">默认</string>
|
||||
|
|
@ -82,7 +72,7 @@
|
|||
<string name="pref_view_on_github_desc">查看源代码、提交错误报告和改进建议</string>
|
||||
<string name="tip_discard_changes">退出编辑后将无法找回你修改过的数据。确定退出编辑吗?</string>
|
||||
<string name="sorting_sequential">添加先后顺序</string>
|
||||
<string name="sorting_subject">学科</string>
|
||||
<string name="sorting_category">类别</string>
|
||||
<string name="sorting_priority">优先级</string>
|
||||
<string name="sorting_completion">完成状态</string>
|
||||
<string name="sorting_alphabetical_ascending">首字母(升序)</string>
|
||||
|
|
@ -106,9 +96,16 @@
|
|||
<string name="pref_secure_mode">安全模式</string>
|
||||
<string name="pref_secure_mode_desc">阻止截屏并保护后台预览图</string>
|
||||
<string name="label_more">更多</string>
|
||||
<string name="happy_birthday">待办1岁生日快乐</string>
|
||||
<string name="subject_customization">自定义</string>
|
||||
<string name="label_enter_subject_name">输入学科名称</string>
|
||||
<string name="label_enter_category_name">输入类别名称</string>
|
||||
<string name="accessibility_progress_tasks">当前共有 %1$d 项任务,其中 %2$d 项已完成,%3$d 项未完成</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>
|
||||
|
|
@ -9,16 +9,6 @@
|
|||
<string name="error_no_content_entered">No content entered</string>
|
||||
<string name="tip_select_this">Select this</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="tip_remain_tasks">%s tasks remaining</string>
|
||||
<string name="page_crash">Oops! App went wrong</string>
|
||||
|
|
@ -40,7 +30,7 @@
|
|||
<string name="pref_developer">Developer</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="label_subject">Subject</string>
|
||||
<string name="label_category">Category</string>
|
||||
<string name="priority_not_urgent">Not Urgent</string>
|
||||
<string name="priority_not_important">Not Important</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="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_subject">Subject</string>
|
||||
<string name="sorting_category">category</string>
|
||||
<string name="sorting_priority">Priority</string>
|
||||
<string name="sorting_completion">Completion</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_desc">Prevent screenshots and protect the background preview image</string>
|
||||
<string name="label_more">More</string>
|
||||
<string name="happy_birthday">Happy 1st birthday to ToDo</string>
|
||||
<string name="subject_customization">Customization</string>
|
||||
<string name="label_enter_subject_name">Enter subject name</string>
|
||||
<string name="label_enter_category_name">Enter category 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_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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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" }
|
||||
Loading…
Reference in a new issue