Fitur undo redo

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-25 17:25:33 +07:00
parent a61c5f45ad
commit 9715d958ae
3 changed files with 76 additions and 5 deletions

View File

@ -264,7 +264,8 @@ fun EditableFullScreenNoteView(
}
}
// Update bagian DraggableMiniMarkdownToolbar call:
// GANTI SELURUH bagian DraggableMiniMarkdownToolbar di EditableFullScreenNoteView.kt
// Letakkan kode ini di dalam Box setelah Scaffold, sebelum closing bracket
if (isContentFocused) {
DraggableMiniMarkdownToolbar(
@ -294,6 +295,8 @@ fun EditableFullScreenNoteView(
isBoldActive = editorState.isBoldActive(),
isItalicActive = editorState.isItalicActive(),
isUnderlineActive = editorState.isUnderlineActive(),
canUndo = editorState.canUndo(),
canRedo = editorState.canRedo(),
onDrag = ::moveToolbar,
onBold = {
@ -307,6 +310,12 @@ fun EditableFullScreenNoteView(
onUnderline = {
ensureFocus()
editorState.toggleUnderline()
},
onUndo = {
editorState.undo()
},
onRedo = {
editorState.redo()
}
)
}

View File

@ -25,11 +25,15 @@ fun DraggableMiniMarkdownToolbar(
isBoldActive: Boolean,
isItalicActive: Boolean,
isUnderlineActive: Boolean,
canUndo: Boolean = false,
canRedo: Boolean = false,
// ACTIONS
onBold: () -> Unit,
onItalic: () -> Unit,
onUnderline: () -> Unit
onUnderline: () -> Unit,
onUndo: () -> Unit = {},
onRedo: () -> Unit = {}
) {
Surface(
modifier = modifier,
@ -80,6 +84,28 @@ fun DraggableMiniMarkdownToolbar(
isActive = isUnderlineActive,
onClick = onUnderline
)
// Divider
Box(
modifier = Modifier
.width(1.dp)
.height(24.dp)
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f))
)
// Undo
ToolbarIcon(
icon = Icons.Default.Undo,
onClick = onUndo,
enabled = canUndo
)
// Redo
ToolbarIcon(
icon = Icons.Default.Redo,
onClick = onRedo,
enabled = canRedo
)
}
}
}
@ -88,10 +114,12 @@ fun DraggableMiniMarkdownToolbar(
private fun ToolbarIcon(
icon: androidx.compose.ui.graphics.vector.ImageVector,
onClick: () -> Unit,
isActive: Boolean = false
isActive: Boolean = false,
enabled: Boolean = true
) {
val activeBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
val activeColor = MaterialTheme.colorScheme.primary
val disabledColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
Box(
modifier = Modifier
@ -104,12 +132,17 @@ private fun ToolbarIcon(
) {
IconButton(
onClick = onClick,
modifier = Modifier.size(36.dp)
modifier = Modifier.size(36.dp),
enabled = enabled
) {
Icon(
icon,
contentDescription = null,
tint = if (isActive) activeColor else MaterialTheme.colorScheme.onSurface,
tint = when {
!enabled -> disabledColor
isActive -> activeColor
else -> MaterialTheme.colorScheme.onSurface
},
modifier = Modifier.size(18.dp)
)
}

View File

@ -17,6 +17,32 @@ class RichEditorState(initial: AnnotatedString) {
)
)
/* =====================
UNDO / REDO
===================== */
private val undoStack = mutableStateListOf<TextFieldValue>()
private val redoStack = mutableStateListOf<TextFieldValue>()
private fun snapshot() {
undoStack.add(value)
redoStack.clear()
}
fun canUndo() = undoStack.isNotEmpty()
fun canRedo() = redoStack.isNotEmpty()
fun undo() {
if (!canUndo()) return
redoStack.add(value)
value = undoStack.removeAt(undoStack.lastIndex)
}
fun redo() {
if (!canRedo()) return
undoStack.add(value)
value = redoStack.removeAt(redoStack.lastIndex)
}
/* =====================
STICKY TYPING STYLE
===================== */
@ -37,6 +63,8 @@ class RichEditorState(initial: AnnotatedString) {
return
}
snapshot()
// Build new annotated string by preserving old spans
val built = buildPreservingSpans(old, newValue)
@ -135,6 +163,7 @@ class RichEditorState(initial: AnnotatedString) {
private fun toggleStyle(style: SpanStyle) {
val sel = value.selection.normalized()
snapshot()
if (!sel.collapsed) applyStyleToSelection(style)
else toggleTypingStyle(style)