feat: 搜索待办
This commit is contained in:
parent
e5601df0b5
commit
87cccfefc1
7 changed files with 292 additions and 90 deletions
|
|
@ -38,7 +38,7 @@ android {
|
|||
applicationId = "cn.super12138.todo"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 989
|
||||
versionCode = 990
|
||||
versionName = "2.3.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
package cn.super12138.todo.ui.pages.tasks
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -11,11 +17,13 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
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
|
||||
|
|
@ -31,15 +39,16 @@ import androidx.navigation3.ui.LocalNavAnimatedContentScope
|
|||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
import cn.super12138.todo.logic.model.Priority
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.components.ConfirmDialog
|
||||
import cn.super12138.todo.ui.components.TodoFloatingActionButton
|
||||
import cn.super12138.todo.ui.components.TopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.tasks.components.TodoCard
|
||||
import cn.super12138.todo.ui.pages.tasks.components.TodoSearchTextField
|
||||
import cn.super12138.todo.ui.pages.tasks.components.TodoTopAppBar
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
import cn.super12138.todo.utils.toLocalDateString
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
|
|
@ -51,22 +60,35 @@ fun SharedTransitionScope.TasksPage(
|
|||
) {
|
||||
val animatedVisibilityScope = LocalNavAnimatedContentScope.current
|
||||
|
||||
val toDoList by viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||
val selectedTodos = viewModel.selectedTodoIds.collectAsState()
|
||||
val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT)
|
||||
|
||||
// 状态持久化
|
||||
val searchFieldState = rememberTextFieldState()
|
||||
val searchMode = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } }
|
||||
val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } }
|
||||
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 } }
|
||||
|
||||
val filteredTodoList = if (searchFieldState.text.isBlank()) toDoList else toDoList.filter {
|
||||
it.content.contains(searchFieldState.text, ignoreCase = true) ||
|
||||
it.category.contains(searchFieldState.text, ignoreCase = true) ||
|
||||
it.dueDate?.toLocalDateString()
|
||||
?.contains(searchFieldState.text, ignoreCase = true) == true
|
||||
}
|
||||
// TODO: 移除已完成任务的过滤及其设置项
|
||||
// if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
|
||||
|
||||
// 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效
|
||||
BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() }
|
||||
|
||||
// 选择时自动退出搜索模式
|
||||
LaunchedEffect(inSelectedMode) { searchMode.value = false }
|
||||
|
||||
TopAppBarScaffold(
|
||||
topBar = {
|
||||
TodoTopAppBar(
|
||||
|
|
@ -74,7 +96,9 @@ fun SharedTransitionScope.TasksPage(
|
|||
selectedMode = inSelectedMode,
|
||||
onCancelSelect = { viewModel.clearAllTodoSelection() },
|
||||
onSelectAll = { viewModel.selectAllTodos() },
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true }
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true },
|
||||
onSearchModeChange = { searchMode.value = it },
|
||||
searchMode = searchMode.value,
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
@ -98,65 +122,84 @@ fun SharedTransitionScope.TasksPage(
|
|||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
||||
modifier = modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = searchMode.value,
|
||||
|
||||
enter = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) + expandVertically(
|
||||
MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
),
|
||||
exit = fadeOut(MaterialTheme.motionScheme.fastEffectsSpec()) + shrinkVertically(
|
||||
MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
),
|
||||
) {
|
||||
TodoSearchTextField(
|
||||
searchMode = searchMode.value,
|
||||
onSearchModeChange = { searchMode.value = it },
|
||||
textFieldState = searchFieldState
|
||||
)
|
||||
}
|
||||
|
||||
if (filteredTodoList.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_task),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(
|
||||
items = filteredTodoList,
|
||||
key = { it.id }
|
||||
) { task ->
|
||||
TodoCard(
|
||||
// id = item.id,
|
||||
content = task.content,
|
||||
category = task.category,
|
||||
completed = task.isCompleted,
|
||||
dueDate = task.dueDate,
|
||||
priority = Priority.fromFloat(task.priority),
|
||||
selected = selectedTodoIds.contains(task.id),
|
||||
onCardClick = {
|
||||
if (inSelectedMode) {
|
||||
viewModel.toggleTodoSelection(task)
|
||||
} else {
|
||||
toTodoEditPage(task)
|
||||
}
|
||||
},
|
||||
onCardLongClick = { viewModel.toggleTodoSelection(task) },
|
||||
onChecked = {
|
||||
viewModel.updateTodo(task.copy(isCompleted = true))
|
||||
viewModel.playConfetti()
|
||||
},
|
||||
modifier = Modifier
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"),
|
||||
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
||||
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
|
||||
)
|
||||
.animateItem(
|
||||
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
|
||||
fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec()
|
||||
)
|
||||
)
|
||||
}
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
|
||||
if (filteredTodoList.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_task),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(
|
||||
items = filteredTodoList,
|
||||
key = { it.id }
|
||||
) { task ->
|
||||
TodoCard(
|
||||
// id = item.id,
|
||||
content = task.content,
|
||||
category = task.category,
|
||||
completed = task.isCompleted,
|
||||
dueDate = task.dueDate,
|
||||
priority = Priority.fromFloat(task.priority),
|
||||
selected = selectedTodoIds.contains(task.id),
|
||||
onCardClick = {
|
||||
if (inSelectedMode) {
|
||||
viewModel.toggleTodoSelection(task)
|
||||
} else {
|
||||
toTodoEditPage(task)
|
||||
}
|
||||
},
|
||||
onCardLongClick = { viewModel.toggleTodoSelection(task) },
|
||||
onChecked = {
|
||||
viewModel.updateTodo(task.copy(isCompleted = true))
|
||||
viewModel.playConfetti()
|
||||
},
|
||||
modifier = Modifier
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"),
|
||||
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
||||
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
|
||||
)
|
||||
.animateItem(
|
||||
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
|
||||
fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec()
|
||||
)
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package cn.super12138.todo.ui.pages.tasks.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
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.utils.VibrationUtils
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TodoSearchTextField(
|
||||
searchMode: Boolean,
|
||||
onSearchModeChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
textFieldState: TextFieldState,
|
||||
) {
|
||||
val view = LocalView.current
|
||||
TextField(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
state = textFieldState,
|
||||
shape = CircleShape,
|
||||
placeholder = { Text(stringResource(R.string.action_search)) },
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
leadingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSearchModeChange(!searchMode)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_back),
|
||||
contentDescription = stringResource(R.string.action_back)
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
AnimatedVisibility(
|
||||
visible = textFieldState.text.isNotBlank(),
|
||||
enter = fadeIn() + scaleIn(),
|
||||
exit = fadeOut() + scaleOut(),
|
||||
) {
|
||||
IconButton(onClick = { textFieldState.setTextAndPlaceCursorAtEnd("") }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_close),
|
||||
contentDescription = stringResource(R.string.action_clear)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package cn.super12138.todo.ui.pages.tasks.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
|
|
@ -8,12 +10,14 @@ import androidx.compose.animation.expandIn
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.shrinkOut
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
|
|
@ -38,8 +42,10 @@ import cn.super12138.todo.utils.VibrationUtils
|
|||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TodoTopAppBar(
|
||||
selectedTodoIds: List<Int>,
|
||||
searchMode: Boolean,
|
||||
selectedMode: Boolean,
|
||||
selectedTodoIds: List<Int>,
|
||||
onSearchModeChange: (Boolean) -> Unit,
|
||||
onCancelSelect: () -> Unit,
|
||||
onSelectAll: () -> Unit,
|
||||
onDeleteSelectedTodo: () -> Unit,
|
||||
|
|
@ -86,6 +92,7 @@ fun TodoTopAppBar(
|
|||
exit = navIconExitTransition
|
||||
) {
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onCancelSelect()
|
||||
|
|
@ -122,37 +129,22 @@ fun TodoTopAppBar(
|
|||
}
|
||||
},
|
||||
actions = {
|
||||
AnimatedVisibility(
|
||||
visible = selectedMode,
|
||||
enter = enterTransition,
|
||||
exit = exitTransition
|
||||
AnimatedContent(
|
||||
targetState = selectedMode,
|
||||
transitionSpec = { titleTransition }
|
||||
) {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSelectAll()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_select_all),
|
||||
contentDescription = stringResource(R.string.tip_select_all)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onDeleteSelectedTodo()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete),
|
||||
contentDescription = stringResource(R.string.action_delete)
|
||||
)
|
||||
}
|
||||
if (it) {
|
||||
ActionMultipleSelection(
|
||||
onSelectAll = onSelectAll,
|
||||
onDeleteSelectedTodo = onDeleteSelectedTodo
|
||||
)
|
||||
} else {
|
||||
ActionSearch(
|
||||
searchMode = searchMode,
|
||||
onSearchModeChange = onSearchModeChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors().copy(containerColor = Color.Transparent),
|
||||
modifier = modifier.drawBehind {
|
||||
|
|
@ -161,11 +153,84 @@ fun TodoTopAppBar(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun AnimatedContentScope.ActionSearch(
|
||||
searchMode: Boolean,
|
||||
onSearchModeChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
BackHandler(enabled = searchMode) { onSearchModeChange(false) }
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = !searchMode,
|
||||
enter = fadeIn() + scaleIn(),
|
||||
exit = fadeOut() + scaleOut(),
|
||||
) {
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSearchModeChange(!searchMode)
|
||||
},
|
||||
modifier = modifier
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_search),
|
||||
contentDescription = stringResource(R.string.action_search)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AnimatedContentScope.ActionMultipleSelection(
|
||||
onSelectAll: () -> Unit,
|
||||
onDeleteSelectedTodo: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val view = LocalView.current
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
modifier = modifier
|
||||
) {
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSelectAll()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_select_all),
|
||||
contentDescription = stringResource(R.string.tip_select_all)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onDeleteSelectedTodo()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete),
|
||||
contentDescription = stringResource(R.string.action_delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(locale = "zh-rCN", showBackground = true)
|
||||
@Composable
|
||||
private fun TodoTopAppBarPreview() {
|
||||
val selectedMode = remember { mutableStateOf(false) }
|
||||
TodoTopAppBar(
|
||||
searchMode = true,
|
||||
onSearchModeChange = {},
|
||||
selectedTodoIds = (1..10).toList(),
|
||||
selectedMode = selectedMode.value,
|
||||
onCancelSelect = { selectedMode.value = !selectedMode.value },
|
||||
|
|
|
|||
9
app/src/main/res/drawable/ic_search.xml
Normal file
9
app/src/main/res/drawable/ic_search.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="M380,640Q271,640 195.5,564.5Q120,489 120,380Q120,271 195.5,195.5Q271,120 380,120Q489,120 564.5,195.5Q640,271 640,380Q640,424 626,463Q612,502 588,532L812,756Q823,767 823,784Q823,801 812,812Q801,823 784,823Q767,823 756,812L532,588Q502,612 463,626Q424,640 380,640ZM380,560Q455,560 507.5,507.5Q560,455 560,380Q560,305 507.5,252.5Q455,200 380,200Q305,200 252.5,252.5Q200,305 200,380Q200,455 252.5,507.5Q305,560 380,560Z" />
|
||||
</vector>
|
||||
|
|
@ -129,4 +129,7 @@
|
|||
<string name="title_all_task">总任务</string>
|
||||
<string name="title_completed_task">已完成</string>
|
||||
<string name="title_pending_task">未完成</string>
|
||||
<string name="action_search">搜索</string>
|
||||
<string name="action_close">关闭</string>
|
||||
<string name="sorting_due_date">截止日期</string>
|
||||
</resources>
|
||||
|
|
@ -130,4 +130,7 @@
|
|||
<string name="title_all_task">All</string>
|
||||
<string name="title_completed_task">Completed</string>
|
||||
<string name="title_pending_task">Pending</string>
|
||||
<string name="action_search">Search</string>
|
||||
<string name="action_close">Close</string>
|
||||
<string name="sorting_due_date">Due date</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue