feat: 优化编辑器逻辑及代码 & 优化添加类别对话框样式

This commit is contained in:
Super12138 2025-07-03 21:54:44 +08:00
parent 29d71fc060
commit 75913080ea
7 changed files with 226 additions and 86 deletions

View file

@ -43,9 +43,6 @@ fun FilterChipGroup(
defaultSelectedItemIndex: Int = 0,
onSelectedChanged: (Int) -> Unit = {}
) {
SideEffect {
Log.d("TAG", "来自 FilterChipGroup重组啦")
}
val view = LocalView.current
var selectedItemIndex by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) }

View file

@ -2,24 +2,15 @@ package cn.super12138.todo.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
@ -28,66 +19,6 @@ import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R
import cn.super12138.todo.utils.VibrationUtils
@Composable
fun FiveCharPromptDialog(
modifier: Modifier = Modifier,
visible: Boolean,
icon: ImageVector = Icons.Outlined.Info,
title: String = stringResource(R.string.tip_tips),
text: String,
confirmButtonText: String = stringResource(R.string.action_save),
showDismissButton: Boolean = true,
dismissButtonText: String = stringResource(R.string.action_cancel),
properties: DialogProperties = DialogProperties(),
onSave: (String) -> Unit,
onDismiss: () -> Unit
) {
val textFieldState = rememberTextFieldState()
var isError by rememberSaveable { mutableStateOf(false) }
val supportingText = listOf(
stringResource(R.string.tip_max_length_5),
stringResource(R.string.error_no_content_entered),
stringResource(R.string.error_exceeds_5_chars)
)
var currentSupportingText by remember { mutableStateOf(supportingText[0]) }
BasicDialog(
visible = visible,
icon = icon,
title = title,
text = { // 已经是实现好滚动的Column布局
Text(text)
OutlinedTextField(
state = textFieldState,
lineLimits = TextFieldLineLimits.SingleLine,
label = { Text(stringResource(R.string.label_enter_sth)) },
supportingText = { Text(currentSupportingText) },
isError = isError
)
},
confirmButton = confirmButtonText,
dismissButton = if (showDismissButton) dismissButtonText else null,
onConfirm = {
if (textFieldState.text.trim().isEmpty()) {
isError = true
currentSupportingText = supportingText[1]
return@BasicDialog
} else if (textFieldState.text.trim().length > 5) {
isError = true
currentSupportingText = supportingText[2]
} else {
onSave(textFieldState.text.toString())
onDismiss()
}
},
onDismiss = onDismiss,
properties = properties,
modifier = modifier
)
}
@Composable
fun ConfirmDialog(
modifier: Modifier = Modifier,

View file

@ -1,6 +1,5 @@
package cn.super12138.todo.ui.pages.editor
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
@ -33,7 +32,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -48,8 +46,8 @@ import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.FilterChipGroup
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.editor.components.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.TodoPrioritySlider
@ -67,6 +65,7 @@ fun TodoEditorPage(
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
// TODO: 本页及其相关组件重组性能检查优化
val view = LocalView.current
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
@ -83,6 +82,7 @@ fun TodoEditorPage(
var defaultIndex by remember { mutableIntStateOf(0) }
LaunchedEffect(originalCategories, toDo) {
if (originalCategories.isEmpty()) return@LaunchedEffect
if (toDo == null) {
val index = if (categories.size == 1) -1 else 0
defaultIndex = index
@ -91,9 +91,7 @@ fun TodoEditorPage(
val index = categories.firstOrNull { it.name == toDo.category }?.id ?: -1
defaultIndex = index
uiState.selectedCategoryIndex = index
if (index == -1) {
uiState.categoryContent = toDo.category
}
if (index != -1) uiState.categoryContent = ""
}
}
@ -174,8 +172,7 @@ fun TodoEditorPage(
value = uiState.toDoContent,
onValueChange = { uiState.toDoContent = it },
isError = uiState.isErrorContent,
modifier = Modifier
.fillMaxWidth()
modifier = Modifier.fillMaxWidth()
)
}
}
@ -186,10 +183,11 @@ fun TodoEditorPage(
style = MaterialTheme.typography.titleMedium
)
FilterChipGroup(
TodoCategoryChip(
items = categories,
defaultSelectedItemIndex = defaultIndex,
onSelectedChanged = { uiState.selectedCategoryIndex = it },
isLoading = originalCategories.isEmpty(),
onCategorySelected = { uiState.selectedCategoryIndex = it },
modifier = Modifier.fillMaxWidth()
)

View file

@ -0,0 +1,41 @@
package cn.super12138.todo.ui.pages.editor.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.FilterChipGroup
@Composable
fun TodoCategoryChip(
modifier: Modifier = Modifier,
items: List<ChipItem>,
defaultSelectedItemIndex: Int,
isLoading: Boolean = false,
onCategorySelected: (Int) -> Unit
) {
Box(modifier = modifier.fillMaxWidth()) {
AnimatedContent(
targetState = isLoading,
transitionSpec = { fadeIn(tween(100)) togetherWith fadeOut(tween(100)) }
) {
if (it) {
CircularProgressIndicator()
} else {
FilterChipGroup(
modifier = Modifier,
items = items,
defaultSelectedItemIndex = defaultSelectedItemIndex,
onSelectedChanged = onCategorySelected
)
}
}
}
}

View file

@ -28,9 +28,9 @@ import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
import cn.super12138.todo.ui.components.FiveCharPromptDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@ -39,6 +39,7 @@ fun SettingsDataCategory(
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier
) {
// TODO: 本页及其相关组件重组性能检查优化
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
@ -77,13 +78,21 @@ fun SettingsDataCategory(
}
} else {
items(items = categories, key = { it }) {
SettingsCategory(it)
CategoryItem(
name = it,
onDelete = { it ->
scope.launch {
DataStoreManager.setCategories(categories - it)
}
},
modifier = Modifier.animateItem()
)
}
}
}
}
FiveCharPromptDialog(
CategoryPromptDialog(
visible = showDialog,
text = stringResource(R.string.tip_enter_category),
onSave = {

View file

@ -0,0 +1,98 @@
package cn.super12138.todo.ui.pages.settings.components.category
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import cn.super12138.todo.R
import cn.super12138.todo.ui.components.BasicDialog
@Composable
fun CategoryPromptDialog(
modifier: Modifier = Modifier,
visible: Boolean,
icon: ImageVector = Icons.Outlined.Info,
title: String = stringResource(R.string.tip_tips),
text: String,
confirmButtonText: String = stringResource(R.string.action_save),
showDismissButton: Boolean = true,
dismissButtonText: String = stringResource(R.string.action_cancel),
properties: DialogProperties = DialogProperties(),
onSave: (String) -> Unit,
onDismiss: () -> Unit
) {
val textFieldState = rememberTextFieldState()
var isError by rememberSaveable { mutableStateOf(false) }
val supportingText = listOf(
stringResource(R.string.tip_max_length_5),
stringResource(R.string.error_no_content_entered),
stringResource(R.string.error_exceeds_5_chars)
)
var currentSupportingText by remember { mutableStateOf(supportingText[0]) }
BasicDialog(
visible = visible,
icon = icon,
title = title,
text = {
// 已经是实现好滚动的Column布局
Text(text)
Spacer(Modifier.size(3.dp))
OutlinedTextField(
state = textFieldState,
lineLimits = TextFieldLineLimits.SingleLine,
label = { Text(stringResource(R.string.label_enter_sth)) },
supportingText = { AnimatedContent(targetState = currentSupportingText) { Text(it) } },
isError = isError
)
},
confirmButton = confirmButtonText,
dismissButton = if (showDismissButton) dismissButtonText else null,
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(trimmedText.toString())
isError = false
currentSupportingText = supportingText[0]
textFieldState.clearText()
onDismiss()
}
}
},
onDismiss = onDismiss,
properties = properties,
modifier = modifier
)
}

View file

@ -0,0 +1,66 @@
package cn.super12138.todo.ui.pages.settings.components.category
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.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
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.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp
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,
onDelete: (String) -> Unit = {}
) {
val view = LocalView.current
Row(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.clip(MaterialTheme.shapes.large)
.padding(
horizontal = TodoDefaults.settingsItemHorizontalPadding,
vertical = TodoDefaults.settingsItemVerticalPadding
),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = name,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge.copy(
color = MaterialTheme.colorScheme.onSurface,
fontSize = 20.sp
),
modifier = Modifier.weight(1f)
)
IconButton(
onClick = {
VibrationUtils.performHapticFeedback(view)
onDelete(name)
}
) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = stringResource(R.string.action_delete)
)
}
}
}