feat: 分类管理页面 MD3E 适配
This commit is contained in:
parent
0fc2383e6c
commit
a487e4b890
5 changed files with 73 additions and 103 deletions
|
|
@ -16,8 +16,10 @@ import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -58,7 +60,12 @@ fun SettingsAboutLicence(
|
||||||
key = { _, library -> library.artifactId }
|
key = { _, library -> library.artifactId }
|
||||||
) { index, library ->
|
) { index, library ->
|
||||||
var openDialog by rememberSaveable { mutableStateOf(false) }
|
var openDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val topRounded by remember { derivedStateOf { index == 0 } }
|
||||||
|
val bottomRounded by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
index == (libraries?.libraries?.size ?: 1) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
SettingsItem(
|
SettingsItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -120,8 +127,8 @@ fun SettingsAboutLicence(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
topRounded = index == 0,
|
topRounded = topRounded,
|
||||||
bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1
|
bottomRounded = bottomRounded
|
||||||
)
|
)
|
||||||
|
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
package cn.super12138.todo.ui.pages.settings
|
package cn.super12138.todo.ui.pages.settings
|
||||||
|
|
||||||
import androidx.compose.animation.core.Spring
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.animation.core.VisibilityThreshold
|
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
|
@ -24,24 +24,29 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||||
import cn.super12138.todo.ui.components.TodoFloatingActionButton
|
import cn.super12138.todo.ui.components.TodoFloatingActionButton
|
||||||
import cn.super12138.todo.ui.components.TopAppBarScaffold
|
import cn.super12138.todo.ui.components.TopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
|
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.pages.settings.components.category.CategoryPromptDialog
|
||||||
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDataCategory(
|
fun SettingsDataCategory(
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
// TODO: 本页及其相关组件重组性能检查优化
|
// TODO: 本页及其相关组件重组性能检查优化
|
||||||
|
val view = LocalView.current
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
@ -53,6 +58,7 @@ fun SettingsDataCategory(
|
||||||
|
|
||||||
val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
val isExpanded by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||||
|
|
||||||
|
// TODO: 取消5字分类限制
|
||||||
TopAppBarScaffold(
|
TopAppBarScaffold(
|
||||||
title = stringResource(R.string.pref_category_category_management),
|
title = stringResource(R.string.pref_category_category_management),
|
||||||
onBack = onNavigateUp,
|
onBack = onNavigateUp,
|
||||||
|
|
@ -70,10 +76,7 @@ fun SettingsDataCategory(
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
SettingsContainer(Modifier.fillMaxSize()) {
|
||||||
state = listState,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
if (categories.isEmpty()) {
|
if (categories.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -84,23 +87,50 @@ fun SettingsDataCategory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(items = categories, key = { it }) {
|
// Keep stable content key (category) for animations, but compute rounding based on content
|
||||||
CategoryItem(
|
items(
|
||||||
name = it,
|
items = categories,
|
||||||
onClick = { category ->
|
key = { it }
|
||||||
|
) { category ->
|
||||||
|
// compute rounding by content rather than by index so insertion/moves
|
||||||
|
// correctly update rounded corners while keeping content key for animations
|
||||||
|
val topRounded = category == categories.first()
|
||||||
|
val bottomRounded = category == categories.last()
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
initialCategory = category
|
initialCategory = category
|
||||||
showDialog = true
|
showDialog = true
|
||||||
},
|
},
|
||||||
onDelete = { category ->
|
topRounded = topRounded,
|
||||||
scope.launch { DataStoreManager.setCategories(categories - category) }
|
bottomRounded = bottomRounded,
|
||||||
},
|
|
||||||
modifier = Modifier.animateItem(
|
modifier = Modifier.animateItem(
|
||||||
fadeInSpec = tween(100),
|
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
||||||
placementSpec = spring(
|
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
|
||||||
stiffness = Spring.StiffnessMediumLow,
|
fadeOutSpec = MaterialTheme.motionScheme.defaultEffectsSpec()
|
||||||
visibilityThreshold = IntOffset.VisibilityThreshold
|
|
||||||
),
|
|
||||||
fadeOutSpec = tween(100)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,12 @@ fun SettingsItem(
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape))
|
.clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape))
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = enableClick, onClick = {
|
enabled = enableClick,
|
||||||
|
onClick = {
|
||||||
VibrationUtils.performHapticFeedback(view)
|
VibrationUtils.performHapticFeedback(view)
|
||||||
onClick()
|
onClick()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.background(background)
|
.background(background)
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = TodoDefaults.settingsItemHorizontalPadding,
|
horizontal = TodoDefaults.settingsItemHorizontalPadding,
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package cn.super12138.todo.ui.pages.settings.components.category
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
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.draw.clip
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.ui.TodoDefaults
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CategoryItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
name: String,
|
|
||||||
onClick: (String) -> Unit = {},
|
|
||||||
onDelete: (String) -> Unit = {}
|
|
||||||
) {
|
|
||||||
val view = LocalView.current
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.clip(MaterialTheme.shapes.large)
|
|
||||||
.clickable(
|
|
||||||
onClick = {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
onClick(name)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.padding(
|
|
||||||
horizontal = TodoDefaults.settingsItemHorizontalPadding,
|
|
||||||
vertical = TodoDefaults.settingsItemVerticalPadding / 2
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
onDelete(name)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_delete),
|
|
||||||
contentDescription = stringResource(R.string.action_delete)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -123,6 +123,8 @@ fun SharedTransitionScope.TasksPage(
|
||||||
items = filteredTodoList,
|
items = filteredTodoList,
|
||||||
key = { _, task -> task.id }
|
key = { _, task -> task.id }
|
||||||
) { index, task ->
|
) { index, task ->
|
||||||
|
val topRounded by remember { derivedStateOf { index == 0 } }
|
||||||
|
val bottomRounded by remember { derivedStateOf { index == filteredTodoList.size - 1 } }
|
||||||
TodoCard(
|
TodoCard(
|
||||||
// id = item.id,
|
// id = item.id,
|
||||||
content = task.content,
|
content = task.content,
|
||||||
|
|
@ -142,8 +144,8 @@ fun SharedTransitionScope.TasksPage(
|
||||||
viewModel.updateTodo(task.copy(isCompleted = true))
|
viewModel.updateTodo(task.copy(isCompleted = true))
|
||||||
viewModel.playConfetti()
|
viewModel.playConfetti()
|
||||||
},
|
},
|
||||||
topRounded = index == 0,
|
topRounded = topRounded,
|
||||||
bottomRounded = index == filteredTodoList.size - 1,
|
bottomRounded = bottomRounded,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sharedBounds(
|
.sharedBounds(
|
||||||
sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"),
|
sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue