feat: 初步增加截止日期功能 & 增加概览页面卡片 & 移除分类5字限制 & 优化导航栏图标
This commit is contained in:
parent
1fabcaab9c
commit
ddb02eb9a3
25 changed files with 572 additions and 141 deletions
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "2ca52b6f8ef7ce2db985716407a7945c",
|
||||
"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, `due_date` INTEGER, `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": "dueDate",
|
||||
"columnName": "due_date",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"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, '2ca52b6f8ef7ce2db985716407a7945c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = 4)
|
||||
@Database(entities = [TodoEntity::class], version = 5)
|
||||
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, MIGRATION_3_4)
|
||||
.addMigrations(MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
|
||||
.fallbackToDestructiveMigration(false)
|
||||
.build()
|
||||
|
||||
|
|
@ -50,5 +50,11 @@ abstract class TodoDatabase : RoomDatabase() {
|
|||
db.execSQL("ALTER TABLE todo_new RENAME TO todo")
|
||||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE ${Constants.DB_TABLE_NAME} ADD COLUMN due_date INTEGER")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,6 @@ data class TodoEntity(
|
|||
@ColumnInfo(name = "category") val category: String = "",
|
||||
@ColumnInfo(name = "completed") val isCompleted: Boolean = false,
|
||||
@ColumnInfo(name = "priority") val priority: Float,
|
||||
@ColumnInfo(name = "due_date") val dueDate: Long? = null,
|
||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ enum class TodoDestinations(
|
|||
Overview(
|
||||
route = TodoScreen.Overview,
|
||||
label = cn.super12138.todo.R.string.page_overview,
|
||||
icon = cn.super12138.todo.R.drawable.ic_view_kanban,
|
||||
selectedIcon = cn.super12138.todo.R.drawable.ic_view_kanban_filled
|
||||
icon = cn.super12138.todo.R.drawable.ic_dashboard,
|
||||
selectedIcon = cn.super12138.todo.R.drawable.ic_dashboard_filled
|
||||
),
|
||||
Tasks(
|
||||
route = TodoScreen.Tasks,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package cn.super12138.todo.ui.navigation
|
|||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
|
|
@ -78,7 +77,7 @@ fun TopNavigation(
|
|||
},
|
||||
entryProvider = entryProvider {
|
||||
entry<TodoScreen.Overview> {
|
||||
OverviewPage()
|
||||
OverviewPage(viewModel = viewModel)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Tasks> {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -42,6 +41,7 @@ import cn.super12138.todo.ui.components.TopAppBarScaffold
|
|||
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip
|
||||
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField
|
||||
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
|
||||
import cn.super12138.todo.ui.pages.editor.components.TodoDueDateChooser
|
||||
import cn.super12138.todo.ui.pages.editor.components.TodoMarkAsCompletedCheckbox
|
||||
import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider
|
||||
import cn.super12138.todo.ui.pages.editor.state.rememberEditorState
|
||||
|
|
@ -81,7 +81,7 @@ fun SharedTransitionScope.TodoEditPage(
|
|||
)
|
||||
|
||||
@Composable
|
||||
private fun TodoEditorPage(
|
||||
fun TodoEditorPage(
|
||||
modifier: Modifier = Modifier,
|
||||
toDo: TodoEntity? = null,
|
||||
onSave: (TodoEntity) -> Unit,
|
||||
|
|
@ -133,53 +133,54 @@ private fun TodoEditorPage(
|
|||
TopAppBarScaffold(
|
||||
title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task),
|
||||
floatingActionButton = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.imePadding()
|
||||
) {
|
||||
if (toDo !== null) {
|
||||
TodoFloatingActionButton(
|
||||
text = stringResource(R.string.action_delete),
|
||||
iconRes = R.drawable.ic_delete,
|
||||
expanded = true,
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
onClick = { uiState.showDeleteConfirmDialog = true }
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.imePadding()
|
||||
) {
|
||||
if (toDo !== null) {
|
||||
TodoFloatingActionButton(
|
||||
text = stringResource(R.string.action_save),
|
||||
iconRes = R.drawable.ic_save,
|
||||
text = stringResource(R.string.action_delete),
|
||||
iconRes = R.drawable.ic_delete,
|
||||
expanded = true,
|
||||
onClick = {
|
||||
if (uiState.setErrorIfNotValid()) {
|
||||
return@TodoFloatingActionButton
|
||||
} else {
|
||||
uiState.clearError()
|
||||
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)
|
||||
}
|
||||
}
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
onClick = { uiState.showDeleteConfirmDialog = true }
|
||||
)
|
||||
}
|
||||
TodoFloatingActionButton(
|
||||
text = stringResource(R.string.action_save),
|
||||
iconRes = R.drawable.ic_save,
|
||||
expanded = true,
|
||||
onClick = {
|
||||
if (uiState.setErrorIfNotValid()) {
|
||||
return@TodoFloatingActionButton
|
||||
} else {
|
||||
uiState.clearError()
|
||||
val newTodo = TodoEntity(
|
||||
id = toDo?.id ?: 0,
|
||||
content = uiState.toDoContent,
|
||||
category = if (isCustomCategory) uiState.categoryContent else categories[uiState.selectedCategoryIndex].name,
|
||||
priority = uiState.priorityState,
|
||||
dueDate = uiState.dueDateState,
|
||||
isCompleted = uiState.isCompleted
|
||||
)
|
||||
onSave(newTodo)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
onBack = ::checkModifiedBeforeBack,
|
||||
modifier = modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item(key = 0) {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
|
||||
item(key=1) {
|
||||
item(key = 1) {
|
||||
TodoContentTextField(
|
||||
value = uiState.toDoContent,
|
||||
onValueChange = { uiState.toDoContent = it },
|
||||
|
|
@ -217,7 +218,7 @@ private fun TodoEditorPage(
|
|||
}
|
||||
}
|
||||
|
||||
item (key = 3){
|
||||
item(key = 3) {
|
||||
Text(
|
||||
text = stringResource(R.string.label_priority),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
|
|
@ -230,12 +231,16 @@ private fun TodoEditorPage(
|
|||
}
|
||||
|
||||
item(key = 4) {
|
||||
Text(
|
||||
text = stringResource(R.string.label_more),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
TodoDueDateChooser(
|
||||
value = uiState.dueDateState,
|
||||
onValueChange = { uiState.dueDateState = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (toDo != null) {
|
||||
Text(
|
||||
text = stringResource(R.string.label_more),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
TodoMarkAsCompletedCheckbox(
|
||||
checked = uiState.isCompleted,
|
||||
onCheckedChange = { uiState.isCompleted = it },
|
||||
|
|
@ -244,28 +249,28 @@ private fun TodoEditorPage(
|
|||
}
|
||||
}
|
||||
|
||||
item (key = 5){
|
||||
item(key = 5) {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmDialog(
|
||||
visible = uiState.showExitConfirmDialog,
|
||||
iconRes = R.drawable.ic_undo,
|
||||
text = stringResource(R.string.tip_discard_changes),
|
||||
onConfirm = {
|
||||
uiState.showExitConfirmDialog = false
|
||||
onNavigateUp()
|
||||
},
|
||||
onDismiss = { uiState.showExitConfirmDialog = false }
|
||||
)
|
||||
|
||||
ConfirmDialog(
|
||||
visible = uiState.showDeleteConfirmDialog,
|
||||
iconRes = R.drawable.ic_delete,
|
||||
text = stringResource(R.string.tip_delete_task, 1),
|
||||
onConfirm = onDelete,
|
||||
onDismiss = { uiState.showDeleteConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmDialog(
|
||||
visible = uiState.showExitConfirmDialog,
|
||||
iconRes = R.drawable.ic_undo,
|
||||
text = stringResource(R.string.tip_discard_changes),
|
||||
onConfirm = {
|
||||
uiState.showExitConfirmDialog = false
|
||||
onNavigateUp()
|
||||
},
|
||||
onDismiss = { uiState.showExitConfirmDialog = false }
|
||||
)
|
||||
|
||||
ConfirmDialog(
|
||||
visible = uiState.showDeleteConfirmDialog,
|
||||
iconRes = R.drawable.ic_delete,
|
||||
text = stringResource(R.string.tip_delete_task, 1),
|
||||
onConfirm = onDelete,
|
||||
onDismiss = { uiState.showDeleteConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package cn.super12138.todo.ui.pages.editor.components
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
import cn.super12138.todo.utils.toLocalDateString
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TodoDueDateChooser(
|
||||
value: Long?,
|
||||
onValueChange: (Long?) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = value)
|
||||
val openDialog = remember { mutableStateOf(false) }
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val pressed by interactionSource.collectIsPressedAsState()
|
||||
LaunchedEffect(pressed) {
|
||||
if (pressed) {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
openDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = value.toLocalDateString(),
|
||||
onValueChange = {},
|
||||
label = { Text("截止日期(可选)") },
|
||||
readOnly = true,
|
||||
interactionSource = interactionSource,
|
||||
modifier = modifier
|
||||
)
|
||||
|
||||
|
||||
/*val interactionSource = remember { MutableInteractionSource() }
|
||||
val pressed by interactionSource.collectIsPressedAsState()
|
||||
val animatedShape = shapeByInteraction(
|
||||
ButtonDefaults.shapes(),
|
||||
pressed,
|
||||
TodoDefaults.shapesDefaultAnimationSpec
|
||||
)*/
|
||||
/*Surface(
|
||||
modifier = modifier
|
||||
.semantics { role = Role.Button }
|
||||
.weight(1f),
|
||||
color = ButtonDefaults.filledTonalButtonColors().containerColor,
|
||||
shape = animatedShape
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.defaultMinSize(
|
||||
minWidth = ButtonDefaults.MinWidth,
|
||||
minHeight = ButtonDefaults.MinHeight,
|
||||
)
|
||||
.combinedClickable(
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
openDialog = true
|
||||
},
|
||||
onLongClick = { onValueChange(null) }
|
||||
)
|
||||
.padding(ButtonDefaults.ContentPadding),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "选择",
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
if (openDialog.value) {
|
||||
DatePickerDialog(
|
||||
confirmButton = {
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onValueChange(datePickerState.selectedDateMillis)
|
||||
openDialog.value = false
|
||||
},
|
||||
shapes = ButtonDefaults.shapes(),
|
||||
) {
|
||||
Text(stringResource(R.string.action_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Row {
|
||||
TextButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onValueChange(null)
|
||||
datePickerState.selectedDateMillis = null
|
||||
},
|
||||
shapes = ButtonDefaults.shapes()
|
||||
) {
|
||||
Text("清除")
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
openDialog.value = false
|
||||
},
|
||||
shapes = ButtonDefaults.shapes()
|
||||
) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = {
|
||||
onValueChange(null)
|
||||
openDialog.value = false
|
||||
}
|
||||
) {
|
||||
DatePicker(
|
||||
state = datePickerState,
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ fun TodoCategoryTextField(
|
|||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
isError: Boolean,
|
||||
supportingText: String = stringResource(R.string.tip_max_length_5),
|
||||
supportingText: String = stringResource(R.string.tip_short_category),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TextField(
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ class EditorState(val initialTodo: TodoEntity? = null) {
|
|||
var categoryContent by mutableStateOf(initialTodo?.category ?: "")
|
||||
var isErrorCategory by mutableStateOf(false)
|
||||
var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f)
|
||||
var dueDateState by mutableStateOf(initialTodo?.dueDate)
|
||||
var isCompleted by mutableStateOf(initialTodo?.isCompleted == true)
|
||||
|
||||
var categorySupportingText by mutableIntStateOf(R.string.tip_max_length_5)
|
||||
var categorySupportingText by mutableIntStateOf(R.string.tip_short_category)
|
||||
private set
|
||||
|
||||
var showExitConfirmDialog by mutableStateOf(false)
|
||||
|
|
@ -33,17 +33,9 @@ class EditorState(val initialTodo: TodoEntity? = null) {
|
|||
*/
|
||||
fun setErrorIfNotValid(): Boolean {
|
||||
isErrorContent = toDoContent.trim().isEmpty()
|
||||
if (selectedCategoryIndex == -1) {
|
||||
if (categoryContent.trim().isEmpty()) {
|
||||
if (selectedCategoryIndex == -1 && categoryContent.trim().isEmpty()) {
|
||||
isErrorCategory = true
|
||||
categorySupportingText = R.string.error_no_content_entered
|
||||
} else if (categoryContent.length > 5) {
|
||||
isErrorCategory = true
|
||||
categorySupportingText = R.string.error_exceeds_5_chars
|
||||
} else {
|
||||
isErrorCategory = false
|
||||
categorySupportingText = R.string.tip_max_length_5
|
||||
}
|
||||
} else {
|
||||
isErrorCategory = false
|
||||
}
|
||||
|
|
@ -67,6 +59,7 @@ class EditorState(val initialTodo: TodoEntity? = null) {
|
|||
if ((initialTodo?.category ?: "") != categoryContent) isModified = true
|
||||
if ((initialTodo?.priority ?: 0f) != priorityState) isModified = true
|
||||
if ((initialTodo?.isCompleted == true) != isCompleted) isModified = true
|
||||
if (initialTodo?.dueDate != dueDateState) isModified = true
|
||||
return isModified
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +76,7 @@ class EditorState(val initialTodo: TodoEntity? = null) {
|
|||
value.categoryContent,
|
||||
value.isErrorCategory,
|
||||
value.priorityState,
|
||||
value.dueDateState,
|
||||
value.isCompleted,
|
||||
value.showExitConfirmDialog,
|
||||
value.showDeleteConfirmDialog
|
||||
|
|
@ -99,9 +93,10 @@ class EditorState(val initialTodo: TodoEntity? = null) {
|
|||
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
|
||||
showDeleteConfirmDialog = list[9] as Boolean
|
||||
dueDateState = list[7] as Long?
|
||||
isCompleted = list[8] as Boolean
|
||||
showExitConfirmDialog = list[9] as Boolean
|
||||
showDeleteConfirmDialog = list[10] as Boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,76 @@
|
|||
package cn.super12138.todo.ui.pages.overview
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.components.TopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.overview.components.RoundedCornerCardLarge
|
||||
import cn.super12138.todo.ui.pages.overview.components.UpcomingTaskCard
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun OverviewPage(modifier: Modifier = Modifier) {
|
||||
fun OverviewPage(
|
||||
viewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val toDos by viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||
val totalTasks by remember { derivedStateOf { toDos.size } }
|
||||
val completedTasks by remember { derivedStateOf { toDos.count { it.isCompleted } } }
|
||||
val nextWeekTodo by remember { derivedStateOf { toDos.filter { it.dueDate != null && it.dueDate < System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 } } }
|
||||
|
||||
TopAppBarScaffold(
|
||||
title = stringResource(R.string.page_overview),
|
||||
modifier = modifier
|
||||
) {
|
||||
Column {
|
||||
Card {
|
||||
Icon(painter = painterResource(R.drawable.ic_ballot), contentDescription = null)
|
||||
Text("总任务")
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
columns = StaggeredGridCells.Adaptive(150.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalItemSpacing = 10.dp
|
||||
) {
|
||||
item {
|
||||
RoundedCornerCardLarge(
|
||||
iconRes = R.drawable.ic_apps,
|
||||
title = "总任务",
|
||||
count = totalTasks
|
||||
)
|
||||
}
|
||||
item {
|
||||
RoundedCornerCardLarge(
|
||||
iconRes = R.drawable.ic_check_circle,
|
||||
title = "已完成",
|
||||
count = completedTasks,
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
}
|
||||
item {
|
||||
RoundedCornerCardLarge(
|
||||
iconRes = R.drawable.ic_pending,
|
||||
title = "未完成",
|
||||
count = totalTasks - completedTasks,
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer // tertiaryContainer
|
||||
)
|
||||
}
|
||||
item {
|
||||
UpcomingTaskCard(
|
||||
nextWeekTodo = nextWeekTodo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package cn.super12138.todo.ui.pages.overview.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.ButtonShapes
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.theme.shapeByInteraction
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun RoundedCornerCardLarge(
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes iconRes: Int,
|
||||
title: String,
|
||||
count: Int,
|
||||
containerColor: Color = TodoDefaults.ContainerColor,
|
||||
shapes: ButtonShapes = TodoDefaults.shapes(),
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val pressed by interactionSource.collectIsPressedAsState()
|
||||
val animatedShape = shapeByInteraction(shapes, pressed, TodoDefaults.shapesDefaultAnimationSpec)
|
||||
|
||||
Card(
|
||||
modifier = modifier.wrapContentHeight(),
|
||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
||||
shape = animatedShape
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
enabled = true,
|
||||
onClick = onClick,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
.padding(TodoDefaults.screenHorizontalPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(iconRes),
|
||||
contentDescription = null
|
||||
)
|
||||
Column(horizontalAlignment = Alignment.Start) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontWeight = FontWeight.ExtraBold
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package cn.super12138.todo.ui.pages.overview.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||
import cn.super12138.todo.utils.toLocalDateString
|
||||
|
||||
@Composable
|
||||
fun UpcomingTaskCard(
|
||||
nextWeekTodo: List<TodoEntity>,
|
||||
containerColor: Color = TodoDefaults.ContainerColor,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier.height(300.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
||||
shape = TodoDefaults.defaultShape
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TodoDefaults.screenHorizontalPadding),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "临近的任务",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(
|
||||
items = nextWeekTodo,
|
||||
key = { it.id }
|
||||
) {
|
||||
SettingsItem(
|
||||
title = it.content,
|
||||
description = it.dueDate.toLocalDateString(),
|
||||
enableClick = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,9 +43,8 @@ fun CategoryPromptDialog(
|
|||
}
|
||||
|
||||
val supportingText = listOf(
|
||||
stringResource(R.string.tip_max_length_5),
|
||||
stringResource(R.string.tip_short_category),
|
||||
stringResource(R.string.error_no_content_entered),
|
||||
stringResource(R.string.error_exceeds_5_chars)
|
||||
)
|
||||
|
||||
var currentSupportingText by remember { mutableStateOf(supportingText[0]) }
|
||||
|
|
@ -70,26 +69,16 @@ fun CategoryPromptDialog(
|
|||
dismissButton = stringResource(R.string.action_cancel),
|
||||
onConfirm = {
|
||||
val trimmedText = textFieldState.text.trim()
|
||||
when {
|
||||
trimmedText.isEmpty() -> {
|
||||
isError = true
|
||||
currentSupportingText = supportingText[1]
|
||||
return@BasicDialog
|
||||
}
|
||||
|
||||
trimmedText.length > 5 -> {
|
||||
isError = true
|
||||
currentSupportingText = supportingText[2]
|
||||
return@BasicDialog
|
||||
}
|
||||
|
||||
else -> {
|
||||
onSave(initialCategory, trimmedText.toString())
|
||||
isError = false
|
||||
currentSupportingText = supportingText[0]
|
||||
textFieldState.clearText()
|
||||
onDismiss()
|
||||
}
|
||||
if (trimmedText.isEmpty()) {
|
||||
isError = true
|
||||
currentSupportingText = supportingText[1]
|
||||
return@BasicDialog
|
||||
} else {
|
||||
onSave(initialCategory, trimmedText.toString())
|
||||
isError = false
|
||||
currentSupportingText = supportingText[0]
|
||||
textFieldState.clearText()
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
onDismiss = onDismiss,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ fun SharedTransitionScope.TasksPage(
|
|||
) {
|
||||
val animatedVisibilityScope = LocalNavAnimatedContentScope.current
|
||||
|
||||
val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||
val selectedTodos = viewModel.selectedTodoIds.collectAsState()
|
||||
val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT)
|
||||
|
||||
|
|
@ -60,9 +59,7 @@ fun SharedTransitionScope.TasksPage(
|
|||
|
||||
val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } }
|
||||
val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } }
|
||||
val toDoList by remember { derivedStateOf { toDos.value } }
|
||||
/*val totalTasks by remember { derivedStateOf { toDoList.size } }
|
||||
val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } }*/
|
||||
val toDoList by viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||
val filteredTodoList =
|
||||
if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
|
||||
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import cn.super12138.todo.logic.model.Priority
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
|
|
@ -68,4 +70,17 @@ fun ContentDrawScope.drawFadedEdge(
|
|||
),
|
||||
blendMode = BlendMode.DstIn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时间戳转换为本地日期字符串
|
||||
*
|
||||
* @receiver Long? 时间戳(单位为毫秒)或 null
|
||||
* @return String 格式化后的日期字符串。如果为传入参数为null则返回空字符串,反之格式为 “yyyy-MM-dd”
|
||||
*/
|
||||
fun Long?.toLocalDateString(): String {
|
||||
if (this == null) return ""
|
||||
val date = Date(this)
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault())
|
||||
return format.format(date)
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_apps.xml
Normal file
9
app/src/main/res/drawable/ic_apps.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M183.5,776.5Q160,753 160,720Q160,687 183.5,663.5Q207,640 240,640Q273,640 296.5,663.5Q320,687 320,720Q320,753 296.5,776.5Q273,800 240,800Q207,800 183.5,776.5ZM423.5,776.5Q400,753 400,720Q400,687 423.5,663.5Q447,640 480,640Q513,640 536.5,663.5Q560,687 560,720Q560,753 536.5,776.5Q513,800 480,800Q447,800 423.5,776.5ZM663.5,776.5Q640,753 640,720Q640,687 663.5,663.5Q687,640 720,640Q753,640 776.5,663.5Q800,687 800,720Q800,753 776.5,776.5Q753,800 720,800Q687,800 663.5,776.5ZM183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560Q207,560 183.5,536.5ZM423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560Q447,560 423.5,536.5ZM663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Q687,560 663.5,536.5ZM183.5,296.5Q160,273 160,240Q160,207 183.5,183.5Q207,160 240,160Q273,160 296.5,183.5Q320,207 320,240Q320,273 296.5,296.5Q273,320 240,320Q207,320 183.5,296.5ZM423.5,296.5Q400,273 400,240Q400,207 423.5,183.5Q447,160 480,160Q513,160 536.5,183.5Q560,207 560,240Q560,273 536.5,296.5Q513,320 480,320Q447,320 423.5,296.5ZM663.5,296.5Q640,273 640,240Q640,207 663.5,183.5Q687,160 720,160Q753,160 776.5,183.5Q800,207 800,240Q800,273 776.5,296.5Q753,320 720,320Q687,320 663.5,296.5Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_check_circle.xml
Normal file
9
app/src/main/res/drawable/ic_check_circle.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M424,552L338,466Q327,455 310,455Q293,455 282,466Q271,477 271,494Q271,511 282,522L396,636Q408,648 424,648Q440,648 452,636L678,410Q689,399 689,382Q689,365 678,354Q667,343 650,343Q633,343 622,354L424,552ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_dashboard.xml
Normal file
9
app/src/main/res/drawable/ic_dashboard.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M520,320L520,160Q520,143 531.5,131.5Q543,120 560,120L800,120Q817,120 828.5,131.5Q840,143 840,160L840,320Q840,337 828.5,348.5Q817,360 800,360L560,360Q543,360 531.5,348.5Q520,337 520,320ZM120,480L120,160Q120,143 131.5,131.5Q143,120 160,120L400,120Q417,120 428.5,131.5Q440,143 440,160L440,480Q440,497 428.5,508.5Q417,520 400,520L160,520Q143,520 131.5,508.5Q120,497 120,480ZM520,800L520,480Q520,463 531.5,451.5Q543,440 560,440L800,440Q817,440 828.5,451.5Q840,463 840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L560,840Q543,840 531.5,828.5Q520,817 520,800ZM120,800L120,640Q120,623 131.5,611.5Q143,600 160,600L400,600Q417,600 428.5,611.5Q440,623 440,640L440,800Q440,817 428.5,828.5Q417,840 400,840L160,840Q143,840 131.5,828.5Q120,817 120,800ZM200,440L360,440L360,200L200,200L200,440ZM600,760L760,760L760,520L600,520L600,760ZM600,280L760,280L760,200L600,200L600,280ZM200,760L360,760L360,680L200,680L200,760ZM360,440L360,440L360,440L360,440ZM600,280L600,280L600,280L600,280ZM600,520L600,520L600,520L600,520ZM360,680L360,680L360,680L360,680Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_dashboard_filled.xml
Normal file
9
app/src/main/res/drawable/ic_dashboard_filled.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M560,360Q543,360 531.5,348.5Q520,337 520,320L520,160Q520,143 531.5,131.5Q543,120 560,120L800,120Q817,120 828.5,131.5Q840,143 840,160L840,320Q840,337 828.5,348.5Q817,360 800,360L560,360ZM160,520Q143,520 131.5,508.5Q120,497 120,480L120,160Q120,143 131.5,131.5Q143,120 160,120L400,120Q417,120 428.5,131.5Q440,143 440,160L440,480Q440,497 428.5,508.5Q417,520 400,520L160,520ZM560,840Q543,840 531.5,828.5Q520,817 520,800L520,480Q520,463 531.5,451.5Q543,440 560,440L800,440Q817,440 828.5,451.5Q840,463 840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L560,840ZM160,840Q143,840 131.5,828.5Q120,817 120,800L120,640Q120,623 131.5,611.5Q143,600 160,600L400,600Q417,600 428.5,611.5Q440,623 440,640L440,800Q440,817 428.5,828.5Q417,840 400,840L160,840Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_pending.xml
Normal file
9
app/src/main/res/drawable/ic_pending.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M322.5,522.5Q340,505 340,480Q340,455 322.5,437.5Q305,420 280,420Q255,420 237.5,437.5Q220,455 220,480Q220,505 237.5,522.5Q255,540 280,540Q305,540 322.5,522.5ZM522.5,522.5Q540,505 540,480Q540,455 522.5,437.5Q505,420 480,420Q455,420 437.5,437.5Q420,455 420,480Q420,505 437.5,522.5Q455,540 480,540Q505,540 522.5,522.5ZM722.5,522.5Q740,505 740,480Q740,455 722.5,437.5Q705,420 680,420Q655,420 637.5,437.5Q620,455 620,480Q620,505 637.5,522.5Q655,540 680,540Q705,540 722.5,522.5ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M280,680L360,680L360,280L280,280L280,680ZM600,600L680,600L680,280L600,280L600,600ZM440,480L520,480L520,280L440,280L440,480ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z" />
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM280,680L360,680L360,280L280,280L280,680ZM600,600L680,600L680,280L600,280L600,600ZM440,480L520,480L520,280L440,280L440,480Z" />
|
||||
</vector>
|
||||
|
|
@ -105,8 +105,6 @@
|
|||
<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>-->
|
||||
<string name="tip_no_category_chip">لا توجد فئات مخصصة حاليًا. يمكنك إضافتها من الإعدادات.</string>
|
||||
|
|
|
|||
|
|
@ -104,8 +104,6 @@
|
|||
<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>-->
|
||||
<string name="tip_no_category_chip">当前暂无自定义分类,你可以在设置中添加分类</string>
|
||||
|
|
@ -118,4 +116,5 @@
|
|||
<string name="tip_selected">已选择</string>
|
||||
<string name="page_tasks">待办</string>
|
||||
<string name="page_overview">概览</string>
|
||||
<string name="tip_short_category">尽量使用简短的分类名称</string>
|
||||
</resources>
|
||||
|
|
@ -105,8 +105,6 @@
|
|||
<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>-->
|
||||
<string name="tip_no_category_chip">There are currently no custom categories. You can add categories in the settings.</string>
|
||||
|
|
@ -119,4 +117,5 @@
|
|||
<string name="tip_selected">Selected</string>
|
||||
<string name="page_tasks">Tasks</string>
|
||||
<string name="page_overview">Overview</string>
|
||||
<string name="tip_short_category">Keep category names concise</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue