feat: 增加列表的空项目提示 & 优化转场动画 & 提升待办列表性能
This commit is contained in:
parent
d3cb35a631
commit
ca32744cdc
19 changed files with 382 additions and 189 deletions
|
|
@ -38,7 +38,7 @@ android {
|
|||
applicationId = "cn.super12138.todo"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 991
|
||||
versionCode = 992
|
||||
versionName = "2.3.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package cn.super12138.todo.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
|
||||
enum class EmptyTipType {
|
||||
Search,
|
||||
List,
|
||||
TaskCompleted
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmptyTip(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 48.dp,
|
||||
type: EmptyTipType
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.padding(TodoDefaults.screenHorizontalPadding)
|
||||
.size(size)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = when (type) {
|
||||
EmptyTipType.List -> R.drawable.ic_list_no_item
|
||||
EmptyTipType.Search -> R.drawable.ic_search_not_found
|
||||
EmptyTipType.TaskCompleted -> R.drawable.ic_thumb_up
|
||||
}
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.size(size / 2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package cn.super12138.todo.ui.components
|
|||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package cn.super12138.todo.ui.navigation
|
|||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -21,12 +22,12 @@ import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptionsPadding
|
|||
import cn.super12138.todo.ui.pages.settings.SettingsInterface
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsMain
|
||||
import cn.super12138.todo.ui.pages.tasks.TasksPage
|
||||
import cn.super12138.todo.ui.theme.fadeThrough
|
||||
import cn.super12138.todo.ui.theme.fadeScale
|
||||
import cn.super12138.todo.ui.theme.materialSharedAxisX
|
||||
import cn.super12138.todo.ui.theme.veilFade
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TopNavigation(
|
||||
backStack: TopLevelBackStack<NavKey>,
|
||||
|
|
@ -64,25 +65,18 @@ fun TopNavigation(
|
|||
)
|
||||
}
|
||||
|
||||
val defaultTransition = fadeScale(
|
||||
effectSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||
spatialSpec = MaterialTheme.motionScheme.defaultSpatialSpec()
|
||||
)
|
||||
|
||||
SharedTransitionLayout {
|
||||
NavDisplay(
|
||||
backStack = backStack.backStack,
|
||||
onBack = ::onBack,
|
||||
/*transitionSpec = {
|
||||
fadeIn() togetherWith veilOut(targetColor = veilColor)
|
||||
},
|
||||
popTransitionSpec = {
|
||||
unveilIn(initialColor = veilColor) togetherWith fadeOut()
|
||||
},
|
||||
predictivePopTransitionSpec = {
|
||||
unveilIn(initialColor = veilColor) togetherWith fadeOut()
|
||||
},*/
|
||||
/**
|
||||
* 来自:https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/transition/MaterialFadeThrough.java#L33
|
||||
*/
|
||||
transitionSpec = { fadeThrough() },
|
||||
popTransitionSpec = { fadeThrough() },
|
||||
predictivePopTransitionSpec = { fadeThrough() },
|
||||
transitionSpec = { defaultTransition },
|
||||
popTransitionSpec = { defaultTransition },
|
||||
predictivePopTransitionSpec = { defaultTransition },
|
||||
entryProvider = entryProvider {
|
||||
entry<TodoScreen.Overview> {
|
||||
OverviewPage(viewModel = viewModel)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
package cn.super12138.todo.ui.pages.overview.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
|
|
@ -23,12 +26,16 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.components.EmptyTip
|
||||
import cn.super12138.todo.ui.components.EmptyTipType
|
||||
import cn.super12138.todo.ui.theme.fadeScale
|
||||
import cn.super12138.todo.utils.containerColor
|
||||
import cn.super12138.todo.utils.toRelativeTimeString
|
||||
|
||||
|
|
@ -54,20 +61,43 @@ fun UpcomingTaskCard(
|
|||
text = stringResource(R.string.title_upcoming_task),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
val transitionSpec = fadeScale()
|
||||
AnimatedContent(
|
||||
targetState = nextWeekTodo.isEmpty(),
|
||||
transitionSpec = { transitionSpec }
|
||||
) {
|
||||
items(
|
||||
items = nextWeekTodo,
|
||||
key = { it.id }
|
||||
) {
|
||||
UpcomingTaskItem(
|
||||
content = it.content,
|
||||
category = it.category,
|
||||
priority = Priority.fromFloat(it.priority),
|
||||
dueDate = it.dueDate
|
||||
)
|
||||
if (it) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.weight(1f) // 占满剩余空间
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
EmptyTip(type = EmptyTipType.List)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_task_brief),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(
|
||||
items = nextWeekTodo,
|
||||
key = { task -> task.id }
|
||||
) { task ->
|
||||
UpcomingTaskItem(
|
||||
content = task.content,
|
||||
category = task.category,
|
||||
priority = Priority.fromFloat(task.priority),
|
||||
dueDate = task.dueDate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
|
|
@ -23,19 +28,24 @@ 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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
import cn.super12138.todo.ui.components.EmptyTip
|
||||
import cn.super12138.todo.ui.components.EmptyTipType
|
||||
import cn.super12138.todo.ui.components.TodoFloatingActionButton
|
||||
import cn.super12138.todo.ui.components.TopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
|
||||
import cn.super12138.todo.ui.theme.fadeScale
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -58,7 +68,8 @@ fun SettingsDataCategory(
|
|||
|
||||
val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||
|
||||
// TODO: 取消5字分类限制
|
||||
val transitionSpec = fadeScale()
|
||||
|
||||
TopAppBarScaffold(
|
||||
title = stringResource(R.string.pref_category_category_management),
|
||||
onBack = onNavigateUp,
|
||||
|
|
@ -76,56 +87,71 @@ fun SettingsDataCategory(
|
|||
},
|
||||
modifier = modifier,
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
if (categories.isEmpty()) {
|
||||
item {
|
||||
AnimatedContent(
|
||||
targetState = categories.isEmpty(),
|
||||
transitionSpec = { transitionSpec }
|
||||
) {
|
||||
if (it) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
EmptyTip(
|
||||
type = EmptyTipType.List,
|
||||
size = 96.dp
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_category_page),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Keep stable content key (category) for animations, but compute rounding based on content
|
||||
items(
|
||||
items = categories,
|
||||
key = { it }
|
||||
) { category ->
|
||||
SettingsItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = category,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.basicMarquee()
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
FilledTonalIconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
scope.launch { DataStoreManager.setCategories(categories - category) }
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete),
|
||||
contentDescription = stringResource(R.string.action_delete)
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = categories,
|
||||
key = { category -> category }
|
||||
) { category ->
|
||||
SettingsItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = category,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.basicMarquee()
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
initialCategory = category
|
||||
showDialog = true
|
||||
},
|
||||
modifier = Modifier.animateItem(
|
||||
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
|
||||
fadeOutSpec = MaterialTheme.motionScheme.defaultEffectsSpec()
|
||||
},
|
||||
trailingContent = {
|
||||
FilledTonalIconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
scope.launch { DataStoreManager.setCategories(categories - category) }
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete),
|
||||
contentDescription = stringResource(R.string.action_delete)
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
initialCategory = category
|
||||
showDialog = true
|
||||
},
|
||||
modifier = Modifier.animateItem(
|
||||
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
|
||||
fadeOutSpec = MaterialTheme.motionScheme.defaultEffectsSpec()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cn.super12138.todo.ui.pages.tasks
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.expandVertically
|
||||
|
|
@ -16,8 +17,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -35,6 +36,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation3.ui.LocalNavAnimatedContentScope
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
|
|
@ -42,11 +44,14 @@ import cn.super12138.todo.logic.database.TodoEntity
|
|||
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.EmptyTip
|
||||
import cn.super12138.todo.ui.components.EmptyTipType
|
||||
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.theme.fadeScale
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
import cn.super12138.todo.utils.toLocalDateString
|
||||
|
||||
|
|
@ -64,30 +69,27 @@ fun SharedTransitionScope.TasksPage(
|
|||
val selectedTodos = viewModel.selectedTodoIds.collectAsState()
|
||||
|
||||
// 状态持久化
|
||||
val searchFieldState = rememberTextFieldState()
|
||||
val searchMode = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val searchFieldState = viewModel.searchFieldState
|
||||
var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } }
|
||||
val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } }
|
||||
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||
val expandedFab by remember { derivedStateOf { viewModel.toDoListState.firstVisibleItemIndex == 0 } }
|
||||
|
||||
val filteredTodoList = if (searchFieldState.text.isBlank()) toDoList else toDoList.filter {
|
||||
val filteredTodoList = if (viewModel.searchMode.value) 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 }
|
||||
} else toDoList
|
||||
|
||||
val transitionSpec = fadeScale()
|
||||
|
||||
// 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效
|
||||
BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() }
|
||||
|
||||
// 选择时自动退出搜索模式
|
||||
LaunchedEffect(inSelectedMode) { searchMode.value = false }
|
||||
LaunchedEffect(inSelectedMode) { if (inSelectedMode) viewModel.searchMode.value = false }
|
||||
|
||||
TopAppBarScaffold(
|
||||
topBar = {
|
||||
|
|
@ -97,8 +99,8 @@ fun SharedTransitionScope.TasksPage(
|
|||
onCancelSelect = { viewModel.clearAllTodoSelection() },
|
||||
onSelectAll = { viewModel.selectAllTodos() },
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true },
|
||||
onSearchModeChange = { searchMode.value = it },
|
||||
searchMode = searchMode.value,
|
||||
onSearchModeChange = { viewModel.searchMode.value = it },
|
||||
searchMode = viewModel.searchMode.value,
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
@ -124,8 +126,7 @@ fun SharedTransitionScope.TasksPage(
|
|||
) {
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = searchMode.value,
|
||||
|
||||
visible = viewModel.searchMode.value,
|
||||
enter = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) + expandVertically(
|
||||
MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
),
|
||||
|
|
@ -134,80 +135,97 @@ fun SharedTransitionScope.TasksPage(
|
|||
),
|
||||
) {
|
||||
TodoSearchTextField(
|
||||
searchMode = searchMode.value,
|
||||
onSearchModeChange = { searchMode.value = it },
|
||||
searchMode = viewModel.searchMode.value,
|
||||
onSearchModeChange = { viewModel.searchMode.value = it },
|
||||
textFieldState = searchFieldState
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
AnimatedContent(
|
||||
targetState = filteredTodoList.isEmpty(),
|
||||
transitionSpec = { transitionSpec }
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
if (it) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.weight(1f) // 占满剩余空间
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
EmptyTip(
|
||||
type = if (viewModel.searchMode.value) EmptyTipType.Search else EmptyTipType.TaskCompleted,
|
||||
size = 96.dp
|
||||
)
|
||||
|
||||
if (filteredTodoList.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_task),
|
||||
text = stringResource(if (viewModel.searchMode.value) R.string.tip_search_task_not_found else 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))
|
||||
LazyColumn(
|
||||
state = viewModel.toDoListState,
|
||||
verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
|
||||
}
|
||||
|
||||
items(
|
||||
items = filteredTodoList,
|
||||
key = { task -> task.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfirmDialog(
|
||||
visible = showDeleteConfirmDialog,
|
||||
iconRes = R.drawable.ic_delete,
|
||||
text = stringResource(R.string.tip_delete_task, selectedTodoIds.size),
|
||||
onConfirm = { viewModel.deleteSelectedTodo() },
|
||||
onDismiss = { showDeleteConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
ConfirmDialog(
|
||||
visible = showDeleteConfirmDialog,
|
||||
iconRes = R.drawable.ic_delete,
|
||||
text = stringResource(R.string.tip_delete_task, selectedTodoIds.size),
|
||||
onConfirm = { viewModel.deleteSelectedTodo() },
|
||||
onDismiss = { showDeleteConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
|
|
@ -75,7 +75,6 @@ fun TodoCard(
|
|||
) {
|
||||
val view = LocalView.current
|
||||
val context = LocalContext.current
|
||||
// TODO: 滑动删除
|
||||
val cardColors = CardDefaults.cardColors(containerColor = TodoDefaults.Colors.Container)
|
||||
val animatedContainerColor by animateColorAsState(targetValue = if (selected) MaterialTheme.colorScheme.secondaryContainer else if (completed) cardColors.disabledContainerColor else cardColors.containerColor)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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
|
||||
|
|
@ -24,7 +23,6 @@ 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
|
||||
|
||||
|
|
@ -51,9 +49,9 @@ fun TodoSearchTextField(
|
|||
leadingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSearchModeChange(!searchMode)
|
||||
}
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onSearchModeChange(!searchMode)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_back),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ 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
|
||||
import androidx.compose.animation.expandIn
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -37,6 +35,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.theme.fadeScale
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
|
|
@ -57,22 +56,15 @@ fun TodoTopAppBar(
|
|||
animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(),
|
||||
expandFrom = Alignment.CenterStart
|
||||
)
|
||||
|
||||
val navIconExitTransition = fadeOut(
|
||||
animationSpec = MaterialTheme.motionScheme.fastEffectsSpec()
|
||||
) + shrinkOut(
|
||||
animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(),
|
||||
shrinkTowards = Alignment.CenterStart
|
||||
)
|
||||
val enterTransition = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) +
|
||||
scaleIn(
|
||||
animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(),
|
||||
initialScale = 0.92f
|
||||
)
|
||||
val exitTransition = fadeOut(MaterialTheme.motionScheme.fastEffectsSpec())
|
||||
val titleTransition = ContentTransform(
|
||||
targetContentEnter = enterTransition,
|
||||
initialContentExit = exitTransition
|
||||
)
|
||||
|
||||
val defaultTransitionSpec = fadeScale()
|
||||
|
||||
val view = LocalView.current
|
||||
val animatedContainerColor by animateColorAsState(
|
||||
|
|
@ -108,7 +100,7 @@ fun TodoTopAppBar(
|
|||
title = {
|
||||
AnimatedContent(
|
||||
targetState = !selectedMode,
|
||||
transitionSpec = { titleTransition }
|
||||
transitionSpec = { defaultTransitionSpec }
|
||||
) {
|
||||
if (it) {
|
||||
Text(
|
||||
|
|
@ -131,7 +123,7 @@ fun TodoTopAppBar(
|
|||
actions = {
|
||||
AnimatedContent(
|
||||
targetState = selectedMode,
|
||||
transitionSpec = { titleTransition }
|
||||
transitionSpec = { defaultTransitionSpec }
|
||||
) {
|
||||
if (it) {
|
||||
ActionMultipleSelection(
|
||||
|
|
@ -155,7 +147,7 @@ fun TodoTopAppBar(
|
|||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun AnimatedContentScope.ActionSearch(
|
||||
private fun ActionSearch(
|
||||
searchMode: Boolean,
|
||||
onSearchModeChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
|
|
@ -166,8 +158,8 @@ private fun AnimatedContentScope.ActionSearch(
|
|||
|
||||
AnimatedVisibility(
|
||||
visible = !searchMode,
|
||||
enter = fadeIn() + scaleIn(),
|
||||
exit = fadeOut() + scaleOut(),
|
||||
enter = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) + scaleIn(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||
exit = fadeOut(MaterialTheme.motionScheme.fastEffectsSpec()) + scaleOut(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||
) {
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
|
|
@ -187,7 +179,7 @@ private fun AnimatedContentScope.ActionSearch(
|
|||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AnimatedContentScope.ActionMultipleSelection(
|
||||
fun ActionMultipleSelection(
|
||||
onSelectAll: () -> Unit,
|
||||
onDeleteSelectedTodo: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.animation.ExitTransition
|
|||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -15,6 +16,9 @@ import androidx.compose.animation.slideInHorizontally
|
|||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.unveilIn
|
||||
import androidx.compose.animation.veilOut
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
|
|
@ -93,7 +97,7 @@ fun materialSharedAxisXOut(
|
|||
)
|
||||
)
|
||||
|
||||
fun fadeThrough(
|
||||
/*fun fadeThrough(
|
||||
durationMillis: Int = 200,
|
||||
): ContentTransform = ContentTransform(
|
||||
fadeThroughIn(durationMillis = durationMillis),
|
||||
|
|
@ -118,7 +122,7 @@ fun fadeThroughOut(
|
|||
durationMillis = durationMillis,
|
||||
easing = FastOutLinearInEasing
|
||||
)
|
||||
)
|
||||
)*/
|
||||
|
||||
fun veilFade(
|
||||
initialColor: Color,
|
||||
|
|
@ -151,4 +155,34 @@ fun veilFadeOut(
|
|||
): ExitTransition =
|
||||
fadeOut() + veilOut(
|
||||
targetColor = initialColor
|
||||
)
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun fadeScaleIn(
|
||||
effectSpec: FiniteAnimationSpec<Float> = MaterialTheme.motionScheme.fastEffectsSpec(),
|
||||
spatialSpec: FiniteAnimationSpec<Float> = MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
): EnterTransition = fadeIn(effectSpec) +
|
||||
scaleIn(
|
||||
animationSpec = spatialSpec,
|
||||
initialScale = 0.92f
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun fadeScaleOut(
|
||||
effectSpec: FiniteAnimationSpec<Float> = MaterialTheme.motionScheme.fastEffectsSpec()
|
||||
): ExitTransition = fadeOut(effectSpec)
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun fadeScale(
|
||||
effectSpec: FiniteAnimationSpec<Float> = MaterialTheme.motionScheme.fastEffectsSpec(),
|
||||
spatialSpec: FiniteAnimationSpec<Float> = MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
): ContentTransform = ContentTransform(
|
||||
targetContentEnter = fadeScaleIn(
|
||||
effectSpec = effectSpec,
|
||||
spatialSpec = spatialSpec
|
||||
),
|
||||
initialContentExit = fadeScaleOut(effectSpec = effectSpec)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package cn.super12138.todo.ui.viewmodels
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -19,10 +21,13 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -39,8 +44,8 @@ class MainViewModel : ViewModel() {
|
|||
|
||||
// 待办
|
||||
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||
val sortedTodos: Flow<List<TodoEntity>> =
|
||||
DataStoreManager.sortingMethodFlow.flatMapLatest { sortingMethod ->
|
||||
val sortedTodos: StateFlow<List<TodoEntity>> = DataStoreManager.sortingMethodFlow
|
||||
.flatMapLatest { sortingMethod ->
|
||||
toDos.map { list ->
|
||||
when (SortingMethod.fromId(sortingMethod)) {
|
||||
SortingMethod.Sequential -> list.sortedWith(
|
||||
|
|
@ -88,9 +93,18 @@ class MainViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
val showConfetti = mutableStateOf(false)
|
||||
|
||||
val toDoListState = LazyListState()
|
||||
val searchMode = mutableStateOf(false)
|
||||
val searchFieldState = TextFieldState()
|
||||
|
||||
// 多选逻辑参考:https://github.com/X1nto/Mauth
|
||||
private val _selectedTodoIds = MutableStateFlow(listOf<Int>())
|
||||
val selectedTodoIds = _selectedTodoIds.asStateFlow()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,8 @@ package cn.super12138.todo.utils
|
|||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.shape.CornerBasedShape
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
|
|
@ -14,7 +11,6 @@ import androidx.compose.ui.graphics.BlendMode
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.logic.model.Priority
|
||||
|
|
@ -139,7 +135,9 @@ fun Long?.toRelativeTimeString(context: Context): String {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun disabledContentColor(alpha: Float = 0.38f): Color = MaterialTheme.colorScheme.onSurface.copy(alpha = alpha)
|
||||
fun disabledContentColor(alpha: Float = 0.38f): Color =
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = alpha)
|
||||
|
||||
@Composable
|
||||
fun disabledContainerColor(alpha: Float = 0.12f): Color = MaterialTheme.colorScheme.onSurface.copy(alpha = alpha)
|
||||
fun disabledContainerColor(alpha: Float = 0.12f): Color =
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = alpha)
|
||||
14
app/src/main/res/drawable/ic_list_no_item.xml
Normal file
14
app/src/main/res/drawable/ic_list_no_item.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
|
||||
android:pathData="M18,13C17.433,13 16.958,12.808 16.575,12.425C16.192,12.042 16,11.567 16,11C16,10.433 16.192,9.958 16.575,9.575C16.958,9.192 17.433,9 18,9H38C38.567,9 39.042,9.192 39.425,9.575C39.808,9.958 40,10.433 40,11C40,11.567 39.808,12.042 39.425,12.425C39.042,12.808 38.567,13 38,13H18ZM8,39C6.9,39 5.958,38.608 5.175,37.825C4.392,37.042 4,36.1 4,35C4,33.9 4.392,32.958 5.175,32.175C5.958,31.392 6.9,31 8,31C9.1,31 10.042,31.392 10.825,32.175C11.608,32.958 12,33.9 12,35C12,36.1 11.608,37.042 10.825,37.825C10.042,38.608 9.1,39 8,39ZM8,27C6.9,27 5.958,26.608 5.175,25.825C4.392,25.042 4,24.1 4,23C4,21.9 4.392,20.958 5.175,20.175C5.958,19.392 6.9,19 8,19C9.1,19 10.042,19.392 10.825,20.175C11.608,20.958 12,21.9 12,23C12,24.1 11.608,25.042 10.825,25.825C10.042,26.608 9.1,27 8,27ZM5.175,13.825C4.392,13.042 4,12.1 4,11C4,9.9 4.392,8.958 5.175,8.175C5.958,7.392 6.9,7 8,7C9.1,7 10.042,7.392 10.825,8.175C11.608,8.958 12,9.9 12,11C12,12.1 11.608,13.042 10.825,13.825C10.042,14.608 9.1,15 8,15C6.9,15 5.958,14.608 5.175,13.825ZM18,25C17.433,25 16.958,24.808 16.575,24.425C16.192,24.042 16,23.567 16,23C16,22.433 16.192,21.958 16.575,21.575C16.958,21.192 17.433,21 18,21H22C22.567,21 23.042,21.192 23.425,21.575C23.808,21.958 24,22.433 24,23C24,23.567 23.808,24.042 23.425,24.425C23.042,24.808 22.567,25 22,25H18ZM16.575,36.425C16.192,36.042 16,35.567 16,35C16,34.433 16.192,33.958 16.575,33.575C16.958,33.192 17.433,33 18,33C18.567,33 19.042,33.192 19.425,33.575C19.808,33.958 20,34.433 20,35C20,35.567 19.808,36.042 19.425,36.425C19.042,36.808 18.567,37 18,37C17.433,37 16.958,36.808 16.575,36.425Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
|
||||
android:pathData="M35,23C39.971,23 44,27.029 44,32C44,36.971 39.971,41 35,41C30.029,41 26,36.971 26,32C26,27.029 30.029,23 35,23ZM34.778,35.5C34.465,35.5 34.2,35.621 33.983,35.862C33.767,36.104 33.659,36.4 33.659,36.75C33.659,37.1 33.767,37.396 33.983,37.638C34.2,37.879 34.465,38 34.778,38C35.091,38 35.356,37.879 35.572,37.638C35.789,37.396 35.896,37.1 35.896,36.75C35.896,36.4 35.789,36.104 35.572,35.862C35.356,35.621 35.091,35.5 34.778,35.5ZM34.912,26C34.345,26 33.805,26.134 33.29,26.4C32.776,26.667 32.384,27.075 32.115,27.625C32.011,27.825 31.977,28.038 32.015,28.263C32.052,28.488 32.152,28.659 32.316,28.775C32.525,28.909 32.742,28.95 32.966,28.9C33.189,28.85 33.376,28.708 33.525,28.475C33.689,28.225 33.895,28.034 34.141,27.9C34.387,27.767 34.644,27.7 34.912,27.7C35.285,27.7 35.61,27.833 35.886,28.1C36.162,28.366 36.3,28.7 36.3,29.1C36.3,29.466 36.198,29.792 35.997,30.075C35.796,30.358 35.569,30.625 35.315,30.875C34.972,31.208 34.67,31.575 34.409,31.975C34.148,32.375 34.018,32.825 34.018,33.325C34.018,33.558 34.096,33.754 34.252,33.912C34.409,34.07 34.592,34.15 34.801,34.15C35.024,34.15 35.215,34.067 35.371,33.9C35.528,33.734 35.628,33.525 35.673,33.275C35.732,32.925 35.867,32.613 36.076,32.338C36.285,32.063 36.508,31.8 36.747,31.55C37.09,31.183 37.385,30.783 37.631,30.35C37.877,29.917 38,29.433 38,28.9C38,28.05 37.69,27.354 37.071,26.813C36.452,26.271 35.732,26 34.912,26Z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_search_not_found.xml
Normal file
12
app/src/main/res/drawable/ic_search_not_found.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18.549,28.335C18.946,27.892 19.144,27.35 19.144,26.708C19.144,26.067 18.946,25.524 18.549,25.081C18.153,24.638 17.667,24.417 17.093,24.417C16.519,24.417 16.033,24.638 15.637,25.081C15.24,25.524 15.042,26.067 15.042,26.708C15.042,27.35 15.24,27.892 15.637,28.335C16.033,28.778 16.519,29 17.093,29C17.667,29 18.153,28.778 18.549,28.335ZM17.339,10.117C18.023,10.117 18.618,10.361 19.124,10.85C19.629,11.339 19.882,11.95 19.882,12.683C19.882,13.356 19.698,13.951 19.329,14.471C18.959,14.99 18.542,15.479 18.077,15.938C17.448,16.549 16.895,17.221 16.416,17.954C15.938,18.688 15.698,19.513 15.698,20.429C15.698,20.857 15.842,21.216 16.129,21.506C16.416,21.796 16.751,21.942 17.134,21.942C17.544,21.942 17.893,21.789 18.18,21.483C18.467,21.178 18.652,20.796 18.734,20.337C18.843,19.696 19.089,19.123 19.472,18.619C19.855,18.115 20.265,17.633 20.703,17.175C21.332,16.503 21.872,15.769 22.323,14.975C22.774,14.181 23,13.294 23,12.317C23,10.758 22.433,9.483 21.298,8.49C20.163,7.497 18.843,7 17.339,7C16.3,7 15.309,7.244 14.365,7.733C13.422,8.222 12.704,8.971 12.212,9.979C12.02,10.346 11.959,10.735 12.027,11.148C12.095,11.56 12.28,11.874 12.581,12.087C12.964,12.332 13.36,12.408 13.77,12.317C14.181,12.225 14.522,11.965 14.796,11.538C15.097,11.079 15.473,10.728 15.924,10.483C16.375,10.239 16.847,10.117 17.339,10.117Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17.516,34.761C12.621,34.761 8.478,33.064 5.087,29.669C1.696,26.274 0,22.178 0,17.381C0,12.583 1.697,8.487 5.092,5.092C8.487,1.697 12.594,0 17.414,0C22.235,0 26.331,1.697 29.703,5.092C33.075,8.487 34.761,12.586 34.761,17.391C34.761,19.33 34.444,21.205 33.811,23.016C33.177,24.826 32.226,26.523 30.959,28.108L47.389,44.402C47.796,44.789 48,45.282 48,45.88C48,46.479 47.796,46.982 47.389,47.389C46.982,47.796 46.479,48 45.88,48C45.282,48 44.789,47.796 44.402,47.389L28.04,31.027C26.682,32.204 25.098,33.12 23.29,33.777C21.481,34.433 19.557,34.761 17.516,34.761ZM17.448,30.687C21.126,30.687 24.252,29.386 26.826,26.784C29.4,24.181 30.687,21.047 30.687,17.381C30.687,13.714 29.4,10.58 26.826,7.977C24.252,5.375 21.126,4.074 17.448,4.074C13.733,4.074 10.575,5.375 7.975,7.977C5.374,10.58 4.074,13.714 4.074,17.381C4.074,21.047 5.374,24.181 7.975,26.784C10.575,29.386 13.733,30.687 17.448,30.687Z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_thumb_up.xml
Normal file
9
app/src/main/res/drawable/ic_thumb_up.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="M840,320Q872,320 896,344Q920,368 920,400L920,480Q920,487 918,495Q916,503 914,510L794,792Q785,812 764,826Q743,840 720,840L280,840Q280,840 280,840Q280,840 280,840L280,320Q280,320 280,320Q280,320 280,320L520,82Q535,67 555.5,64.5Q576,62 595,72Q614,82 623,100Q632,118 627,137L582,320L840,320ZM360,354L360,760Q360,760 360,760Q360,760 360,760L720,760Q720,760 720,760Q720,760 720,760L840,480L840,400Q840,400 840,400Q840,400 840,400L480,400L534,180L360,354ZM160,840Q127,840 103.5,816.5Q80,793 80,760L80,400Q80,367 103.5,343.5Q127,320 160,320L280,320Q280,320 280,320Q280,320 280,320L280,400L160,400Q160,400 160,400Q160,400 160,400L160,760Q160,760 160,760Q160,760 160,760L280,760L280,840Q280,840 280,840Q280,840 280,840L160,840ZM360,760L360,354L360,354L360,400L360,400Q360,400 360,400Q360,400 360,400L360,480L360,760Q360,760 360,760Q360,760 360,760L360,760Q360,760 360,760Q360,760 360,760Z" />
|
||||
</vector>
|
||||
|
|
@ -108,7 +108,6 @@
|
|||
<string name="tip_enter_category">أدخل الفئة التي تريد إضافتها</string>
|
||||
<!--<string name="error_category_duplicate">الفئة مكررة</string>-->
|
||||
<string name="tip_no_category_chip">لا توجد فئات مخصصة حاليًا. يمكنك إضافتها من الإعدادات.</string>
|
||||
<string name="tip_no_category_page">لا توجد فئات مخصصة في الوقت الحالي.</string>
|
||||
<string name="tip_default_category">الفئة الافتراضية، يرجى تعديلها</string>
|
||||
<string name="label_customization">تخصيص</string>
|
||||
<string name="pref_developer_options">خيارات المطور</string>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
<string name="tip_enter_category">输入你想添加的分类名称</string>
|
||||
<!--<string name="error_category_duplicate">该分类已经存在</string>-->
|
||||
<string name="tip_no_category_chip">当前暂无自定义分类,你可以在设置中添加分类</string>
|
||||
<string name="tip_no_category_page">当前暂无自定义分类</string>
|
||||
<string name="tip_no_category_page">暂无自定义分类</string>
|
||||
<string name="tip_default_category">默认分类,请修改</string>
|
||||
<string name="label_customization">自定义</string>
|
||||
<string name="pref_developer_options">开发者选项</string>
|
||||
|
|
@ -132,4 +132,6 @@
|
|||
<string name="action_search">搜索</string>
|
||||
<string name="action_close">关闭</string>
|
||||
<string name="sorting_due_date">截止日期</string>
|
||||
<string name="tip_search_task_not_found">暂未搜索到内容</string>
|
||||
<string name="tip_no_task_brief">暂无任务</string>
|
||||
</resources>
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
<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>
|
||||
<string name="tip_no_category_page">There are no custom categories at the moment.</string>
|
||||
<string name="tip_no_category_page">No custom categories</string>
|
||||
<string name="tip_default_category">Default Category, please modify</string>
|
||||
<string name="label_customization">Customization</string>
|
||||
<string name="pref_developer_options">Developer Options</string>
|
||||
|
|
@ -133,4 +133,6 @@
|
|||
<string name="action_search">Search</string>
|
||||
<string name="action_close">Close</string>
|
||||
<string name="sorting_due_date">Due date</string>
|
||||
<string name="tip_search_task_not_found">No tasks found</string>
|
||||
<string name="tip_no_task_brief">No tasks</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue