perf: 优化待办编辑器性能
This commit is contained in:
parent
29971d5486
commit
711d8a19c5
5 changed files with 246 additions and 156 deletions
|
|
@ -34,10 +34,10 @@ import cn.super12138.todo.utils.VibrationUtils
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FilterChipGroup(
|
fun FilterChipGroup(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
items: List<ChipItem>,
|
items: List<ChipItem>,
|
||||||
defaultSelectedItemIndex: Int = 0,
|
defaultSelectedItemIndex: Int = 0,
|
||||||
onSelectedChanged: (Int) -> Unit = {},
|
onSelectedChanged: (Int) -> Unit = {}
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
var selectedItemId by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) }
|
var selectedItemId by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) }
|
||||||
|
|
@ -80,10 +80,8 @@ private fun FilterChipItem(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label = {
|
label = { Text(text) },
|
||||||
Text(text)
|
modifier = modifier.padding(end = 10.dp)
|
||||||
},
|
|
||||||
modifier = Modifier.padding(end = 10.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.AnimatedVisibilityScope
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionScope
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
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.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Undo
|
import androidx.compose.material.icons.automirrored.outlined.Undo
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
|
@ -24,17 +24,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -53,6 +47,8 @@ import cn.super12138.todo.ui.components.FilterChipGroup
|
||||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
|
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
|
||||||
import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider
|
import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider
|
||||||
|
import cn.super12138.todo.ui.pages.editor.components.TodoSubjectTextField
|
||||||
|
import cn.super12138.todo.ui.pages.editor.state.rememberEditorState
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
import cn.super12138.todo.utils.VibrationUtils
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
|
|
@ -66,34 +62,16 @@ fun TodoEditorPage(
|
||||||
sharedTransitionScope: SharedTransitionScope,
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
animatedVisibilityScope: AnimatedVisibilityScope
|
animatedVisibilityScope: AnimatedVisibilityScope
|
||||||
) {
|
) {
|
||||||
var showExitConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
|
||||||
var toDoContent by rememberSaveable { mutableStateOf(toDo?.content ?: "") }
|
val uiState = rememberEditorState(initialTodo = toDo)
|
||||||
var isErrorContent by rememberSaveable { mutableStateOf(false) }
|
val isCustomSubject by remember { derivedStateOf { uiState.selectedSubjectId == Subjects.Custom.id } }
|
||||||
var selectedSubjectId by rememberSaveable { mutableIntStateOf(toDo?.subject ?: 0) }
|
|
||||||
var subjectContent by rememberSaveable { mutableStateOf(toDo?.customSubject ?: "") }
|
|
||||||
var isErrorSubject by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var priorityState by rememberSaveable { mutableFloatStateOf(toDo?.priority ?: 0f) }
|
|
||||||
var completedSwitchState by rememberSaveable { mutableStateOf(toDo?.isCompleted == true) }
|
|
||||||
|
|
||||||
val isCustomSubject by remember {
|
|
||||||
derivedStateOf { selectedSubjectId == Subjects.Custom.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkModifiedBeforeBack() {
|
fun checkModifiedBeforeBack() {
|
||||||
var isModified = false
|
if (uiState.isModified()) {
|
||||||
if ((toDo?.content ?: "") != toDoContent) isModified = true
|
uiState.showExitConfirmDialog = true
|
||||||
if ((toDo?.subject ?: 0) != selectedSubjectId) isModified = true
|
|
||||||
if ((toDo?.customSubject ?: "") != subjectContent) isModified = true
|
|
||||||
if ((toDo?.priority ?: 0f) != priorityState) isModified = true
|
|
||||||
if ((toDo?.isCompleted == true) != completedSwitchState) isModified = true
|
|
||||||
if (isModified) {
|
|
||||||
showExitConfirmDialog = true
|
|
||||||
} else {
|
} else {
|
||||||
onNavigateUp()
|
onNavigateUp()
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +93,7 @@ fun TodoEditorPage(
|
||||||
text = stringResource(R.string.action_delete),
|
text = stringResource(R.string.action_delete),
|
||||||
expanded = true,
|
expanded = true,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
onClick = { showDeleteConfirmDialog = true },
|
onClick = { uiState.showDeleteConfirmDialog = true },
|
||||||
modifier = Modifier.imePadding()
|
modifier = Modifier.imePadding()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -124,23 +102,12 @@ fun TodoEditorPage(
|
||||||
text = stringResource(R.string.action_save),
|
text = stringResource(R.string.action_save),
|
||||||
expanded = true,
|
expanded = true,
|
||||||
onClick = {
|
onClick = {
|
||||||
isErrorContent = toDoContent.trim().isEmpty()
|
if (uiState.setErrorIfNotValid()) {
|
||||||
isErrorSubject = subjectContent.trim()
|
return@AnimatedExtendedFloatingActionButton
|
||||||
.isEmpty() && selectedSubjectId == Subjects.Custom.id
|
} else {
|
||||||
if (isErrorContent || isErrorSubject) return@AnimatedExtendedFloatingActionButton
|
uiState.clearError()
|
||||||
|
onSave(uiState.getEntity())
|
||||||
isErrorContent = false
|
}
|
||||||
isErrorSubject = false
|
|
||||||
onSave(
|
|
||||||
TodoEntity(
|
|
||||||
content = toDoContent,
|
|
||||||
subject = selectedSubjectId,
|
|
||||||
customSubject = subjectContent,
|
|
||||||
isCompleted = completedSwitchState,
|
|
||||||
priority = priorityState,
|
|
||||||
id = toDo?.id ?: 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.imePadding()
|
.imePadding()
|
||||||
|
|
@ -155,138 +122,129 @@ fun TodoEditorPage(
|
||||||
onBack = { checkModifiedBeforeBack() },
|
onBack = { checkModifiedBeforeBack() },
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Column(
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(horizontal = TodoDefaults.screenPadding)
|
.padding(horizontal = TodoDefaults.screenPadding)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
with(sharedTransitionScope) {
|
item {
|
||||||
TodoContentTextField(
|
|
||||||
value = toDoContent,
|
|
||||||
onValueChange = { toDoContent = it },
|
|
||||||
isError = isErrorContent,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.sharedBounds(
|
|
||||||
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"),
|
|
||||||
animatedVisibilityScope = animatedVisibilityScope
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.size(5.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.label_subject),
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.size(5.dp))
|
|
||||||
|
|
||||||
val subjects = remember {
|
|
||||||
Subjects.entries.map {
|
|
||||||
ChipItem(
|
|
||||||
id = it.id,
|
|
||||||
text = it.getDisplayName(context)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FilterChipGroup(
|
|
||||||
items = subjects,
|
|
||||||
defaultSelectedItemIndex = toDo?.subject ?: Subjects.Chinese.id,
|
|
||||||
onSelectedChanged = {
|
|
||||||
selectedSubjectId = it
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
AnimatedVisibility(isCustomSubject) {
|
|
||||||
with(sharedTransitionScope) {
|
with(sharedTransitionScope) {
|
||||||
TextField(
|
TodoContentTextField(
|
||||||
value = subjectContent,
|
value = uiState.toDoContent,
|
||||||
onValueChange = { subjectContent = it },
|
onValueChange = { uiState.toDoContent = it },
|
||||||
label = { Text(stringResource(R.string.label_enter_subject_name)) },
|
isError = uiState.isErrorContent,
|
||||||
isError = isErrorSubject,
|
|
||||||
supportingText = {
|
|
||||||
AnimatedVisibility(isErrorSubject) {
|
|
||||||
Text(stringResource(R.string.error_no_content_entered))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.sharedBounds(
|
.sharedBounds(
|
||||||
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_${toDo?.id}"),
|
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"),
|
||||||
animatedVisibilityScope = animatedVisibilityScope
|
animatedVisibilityScope = animatedVisibilityScope
|
||||||
)
|
)
|
||||||
.padding(top = 5.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.size(10.dp))
|
item {
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.label_priority),
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.size(5.dp))
|
|
||||||
|
|
||||||
TodoPrioritySlider(
|
|
||||||
value = priorityState,
|
|
||||||
onValueChange = { priorityState = it },
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.size(10.dp))
|
|
||||||
|
|
||||||
if (toDo != null) {
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.label_more),
|
text = stringResource(R.string.label_subject),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.size(5.dp))
|
val subjects = remember {
|
||||||
|
Subjects.entries.map {
|
||||||
Row(
|
ChipItem(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
id = it.id,
|
||||||
|
text = it.getDisplayName(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FilterChipGroup(
|
||||||
|
items = subjects,
|
||||||
|
defaultSelectedItemIndex = toDo?.subject ?: Subjects.Chinese.id,
|
||||||
|
onSelectedChanged = { uiState.selectedSubjectId = it },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isCustomSubject,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
) {
|
) {
|
||||||
Text(
|
with(sharedTransitionScope) {
|
||||||
text = stringResource(R.string.tip_mark_completed),
|
TodoSubjectTextField(
|
||||||
style = MaterialTheme.typography.labelLarge,
|
value = uiState.subjectContent,
|
||||||
modifier = Modifier.padding(end = 10.dp)
|
onValueChange = { uiState.subjectContent = it },
|
||||||
)
|
isError = uiState.isErrorSubject,
|
||||||
Switch(
|
modifier = Modifier
|
||||||
checked = completedSwitchState,
|
.fillMaxWidth()
|
||||||
onCheckedChange = {
|
.sharedBounds(
|
||||||
completedSwitchState = it
|
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_SUBJECT_TRANSITION}_${toDo?.id}"),
|
||||||
VibrationUtils.performHapticFeedback(view)
|
animatedVisibilityScope = animatedVisibilityScope
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.size(20.dp))
|
item {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.label_priority),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoPrioritySlider(
|
||||||
|
value = { uiState.priorityState },
|
||||||
|
onValueChange = { uiState.priorityState = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
if (toDo != null) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.label_more),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.tip_mark_completed),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
modifier = Modifier.padding(end = 10.dp)
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = uiState.isCompleted,
|
||||||
|
onCheckedChange = {
|
||||||
|
VibrationUtils.performHapticFeedback(view)
|
||||||
|
uiState.isCompleted = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Spacer(Modifier.size(20.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
visible = showExitConfirmDialog,
|
visible = uiState.showExitConfirmDialog,
|
||||||
icon = Icons.AutoMirrored.Outlined.Undo,
|
icon = Icons.AutoMirrored.Outlined.Undo,
|
||||||
text = stringResource(R.string.tip_discard_changes),
|
text = stringResource(R.string.tip_discard_changes),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
showExitConfirmDialog = false
|
uiState.showExitConfirmDialog = false
|
||||||
onNavigateUp()
|
onNavigateUp()
|
||||||
},
|
},
|
||||||
onDismiss = { showExitConfirmDialog = false }
|
onDismiss = { uiState.showExitConfirmDialog = false }
|
||||||
)
|
)
|
||||||
|
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
visible = showDeleteConfirmDialog,
|
visible = uiState.showDeleteConfirmDialog,
|
||||||
icon = Icons.Outlined.Delete,
|
icon = Icons.Outlined.Delete,
|
||||||
text = stringResource(R.string.tip_delete_task, 1),
|
text = stringResource(R.string.tip_delete_task, 1),
|
||||||
onConfirm = onDelete,
|
onConfirm = onDelete,
|
||||||
onDismiss = { showDeleteConfirmDialog = false }
|
onDismiss = { uiState.showDeleteConfirmDialog = false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -28,3 +28,24 @@ fun TodoContentTextField(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TodoSubjectTextField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
isError: Boolean,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
label = { Text(stringResource(R.string.label_enter_subject_name)) },
|
||||||
|
isError = isError,
|
||||||
|
supportingText = {
|
||||||
|
AnimatedVisibility(isError) {
|
||||||
|
Text(stringResource(R.string.error_no_content_entered))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ import cn.super12138.todo.utils.VibrationUtils
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TodoPrioritySlider(
|
fun TodoPrioritySlider(
|
||||||
value: Float,
|
value: () -> Float,
|
||||||
onValueChange: (Float) -> Unit,
|
onValueChange: (Float) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
@ -39,7 +39,7 @@ fun TodoPrioritySlider(
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
||||||
Slider(
|
Slider(
|
||||||
value = value,
|
value = value(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS)
|
VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS)
|
||||||
onValueChange(it)
|
onValueChange(it)
|
||||||
|
|
@ -55,7 +55,7 @@ fun TodoPrioritySlider(
|
||||||
.sizeIn(45.dp, 25.dp)
|
.sizeIn(45.dp, 25.dp)
|
||||||
.wrapContentWidth()
|
.wrapContentWidth()
|
||||||
) {
|
) {
|
||||||
Text(priorityName[Priority.fromFloat(value).ordinal])
|
Text(priorityName[Priority.fromFloat(value()).ordinal])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource
|
||||||
|
|
@ -66,9 +66,9 @@ fun TodoPrioritySlider(
|
||||||
modifier = modifier.semantics {
|
modifier = modifier.semantics {
|
||||||
contentDescription =
|
contentDescription =
|
||||||
context.getString(R.string.label_priority) + priorityName[Priority.fromFloat(
|
context.getString(R.string.label_priority) + priorityName[Priority.fromFloat(
|
||||||
value
|
value()
|
||||||
).ordinal]
|
).ordinal]
|
||||||
stateDescription = priorityName[Priority.fromFloat(value).ordinal]
|
stateDescription = priorityName[Priority.fromFloat(value()).ordinal]
|
||||||
liveRegion = LiveRegionMode.Polite
|
liveRegion = LiveRegionMode.Polite
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package cn.super12138.todo.ui.pages.editor.state
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.SaverScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import cn.super12138.todo.logic.database.TodoEntity
|
||||||
|
import cn.super12138.todo.logic.model.Subjects
|
||||||
|
|
||||||
|
class EditorState(
|
||||||
|
val initialTodo: TodoEntity? = null,
|
||||||
|
) {
|
||||||
|
var toDoContent by mutableStateOf(initialTodo?.content ?: "")
|
||||||
|
var isErrorContent by mutableStateOf(false)
|
||||||
|
var selectedSubjectId by mutableIntStateOf(initialTodo?.subject ?: 0)
|
||||||
|
var subjectContent by mutableStateOf(initialTodo?.customSubject ?: "")
|
||||||
|
var isErrorSubject by mutableStateOf(false)
|
||||||
|
var priorityState by mutableFloatStateOf(initialTodo?.priority ?: 0f)
|
||||||
|
var isCompleted by mutableStateOf(initialTodo?.isCompleted == true)
|
||||||
|
var showExitConfirmDialog by mutableStateOf(false)
|
||||||
|
var showDeleteConfirmDialog by mutableStateOf(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查待办内容和学科的一者是否无有效,如果无效则为文本框设置错误状态
|
||||||
|
*
|
||||||
|
* @return 它们二者是否都有效。有一者无效就返回 true
|
||||||
|
*/
|
||||||
|
fun setErrorIfNotValid(): Boolean {
|
||||||
|
isErrorContent = toDoContent.trim().isEmpty()
|
||||||
|
isErrorSubject = subjectContent.trim().isEmpty() &&
|
||||||
|
selectedSubjectId == Subjects.Custom.id
|
||||||
|
return isErrorContent && isErrorSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除文本框全部错误
|
||||||
|
*/
|
||||||
|
fun clearError() {
|
||||||
|
isErrorContent = false
|
||||||
|
isErrorSubject = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑后的待办实体
|
||||||
|
*
|
||||||
|
* @return TodoEntity 待办实体
|
||||||
|
*/
|
||||||
|
fun getEntity(): TodoEntity = TodoEntity(
|
||||||
|
id = initialTodo?.id ?: 0,
|
||||||
|
content = toDoContent,
|
||||||
|
subject = selectedSubjectId,
|
||||||
|
customSubject = subjectContent,
|
||||||
|
priority = priorityState,
|
||||||
|
isCompleted = isCompleted
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查待办是否被编辑修改
|
||||||
|
*/
|
||||||
|
fun isModified(): Boolean {
|
||||||
|
var isModified = false
|
||||||
|
if ((initialTodo?.content ?: "") != toDoContent) isModified = true
|
||||||
|
if ((initialTodo?.subject ?: 0) != selectedSubjectId) isModified = true
|
||||||
|
if ((initialTodo?.customSubject ?: "") != subjectContent) isModified = true
|
||||||
|
if ((initialTodo?.priority ?: 0f) != priorityState) isModified = true
|
||||||
|
if ((initialTodo?.isCompleted == true) != isCompleted) isModified = true
|
||||||
|
return isModified
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存状态的 Saver 对象,用于适配 rememberSaveable
|
||||||
|
*/
|
||||||
|
object Saver : androidx.compose.runtime.saveable.Saver<EditorState, Any> {
|
||||||
|
override fun SaverScope.save(value: EditorState): Any? {
|
||||||
|
return listOf(
|
||||||
|
value.initialTodo?.id ?: 0,
|
||||||
|
value.toDoContent,
|
||||||
|
value.isErrorContent,
|
||||||
|
value.selectedSubjectId,
|
||||||
|
value.subjectContent,
|
||||||
|
value.isErrorSubject,
|
||||||
|
value.priorityState,
|
||||||
|
value.isCompleted,
|
||||||
|
value.showExitConfirmDialog,
|
||||||
|
value.showDeleteConfirmDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun restore(value: Any): EditorState? {
|
||||||
|
val list = value as List<*>
|
||||||
|
val initialTodo = list[0] as? TodoEntity?
|
||||||
|
return EditorState(initialTodo).apply {
|
||||||
|
toDoContent = list[1] as String
|
||||||
|
isErrorContent = list[2] as Boolean
|
||||||
|
selectedSubjectId = list[3] as Int
|
||||||
|
subjectContent = list[4] as String
|
||||||
|
isErrorSubject = list[5] as Boolean
|
||||||
|
priorityState = list[6] as Float
|
||||||
|
isCompleted = list[7] as Boolean
|
||||||
|
showExitConfirmDialog = list[8] as Boolean
|
||||||
|
showDeleteConfirmDialog = list[9] as Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberEditorState(initialTodo: TodoEntity? = null): EditorState =
|
||||||
|
rememberSaveable(saver = EditorState.Saver) { EditorState(initialTodo) }
|
||||||
Loading…
Reference in a new issue