feat: 优化编辑器逻辑及代码 & 优化添加类别对话框样式
This commit is contained in:
parent
29d71fc060
commit
75913080ea
7 changed files with 226 additions and 86 deletions
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue