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) { if (isContentFocused) {
DraggableMiniMarkdownToolbar( DraggableMiniMarkdownToolbar(
@ -294,6 +295,8 @@ fun EditableFullScreenNoteView(
isBoldActive = editorState.isBoldActive(), isBoldActive = editorState.isBoldActive(),
isItalicActive = editorState.isItalicActive(), isItalicActive = editorState.isItalicActive(),
isUnderlineActive = editorState.isUnderlineActive(), isUnderlineActive = editorState.isUnderlineActive(),
canUndo = editorState.canUndo(),
canRedo = editorState.canRedo(),
onDrag = ::moveToolbar, onDrag = ::moveToolbar,
onBold = { onBold = {
@ -307,6 +310,12 @@ fun EditableFullScreenNoteView(
onUnderline = { onUnderline = {
ensureFocus() ensureFocus()
editorState.toggleUnderline() editorState.toggleUnderline()
},
onUndo = {
editorState.undo()
},
onRedo = {
editorState.redo()
} }
) )
} }

View File

@ -25,11 +25,15 @@ fun DraggableMiniMarkdownToolbar(
isBoldActive: Boolean, isBoldActive: Boolean,
isItalicActive: Boolean, isItalicActive: Boolean,
isUnderlineActive: Boolean, isUnderlineActive: Boolean,
canUndo: Boolean = false,
canRedo: Boolean = false,
// ACTIONS // ACTIONS
onBold: () -> Unit, onBold: () -> Unit,
onItalic: () -> Unit, onItalic: () -> Unit,
onUnderline: () -> Unit onUnderline: () -> Unit,
onUndo: () -> Unit = {},
onRedo: () -> Unit = {}
) { ) {
Surface( Surface(
modifier = modifier, modifier = modifier,
@ -80,6 +84,28 @@ fun DraggableMiniMarkdownToolbar(
isActive = isUnderlineActive, isActive = isUnderlineActive,
onClick = onUnderline 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( private fun ToolbarIcon(
icon: androidx.compose.ui.graphics.vector.ImageVector, icon: androidx.compose.ui.graphics.vector.ImageVector,
onClick: () -> Unit, onClick: () -> Unit,
isActive: Boolean = false isActive: Boolean = false,
enabled: Boolean = true
) { ) {
val activeBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f) val activeBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
val activeColor = MaterialTheme.colorScheme.primary val activeColor = MaterialTheme.colorScheme.primary
val disabledColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
Box( Box(
modifier = Modifier modifier = Modifier
@ -104,12 +132,17 @@ private fun ToolbarIcon(
) { ) {
IconButton( IconButton(
onClick = onClick, onClick = onClick,
modifier = Modifier.size(36.dp) modifier = Modifier.size(36.dp),
enabled = enabled
) { ) {
Icon( Icon(
icon, icon,
contentDescription = null, 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) 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 STICKY TYPING STYLE
===================== */ ===================== */
@ -37,6 +63,8 @@ class RichEditorState(initial: AnnotatedString) {
return return
} }
snapshot()
// Build new annotated string by preserving old spans // Build new annotated string by preserving old spans
val built = buildPreservingSpans(old, newValue) val built = buildPreservingSpans(old, newValue)
@ -135,6 +163,7 @@ class RichEditorState(initial: AnnotatedString) {
private fun toggleStyle(style: SpanStyle) { private fun toggleStyle(style: SpanStyle) {
val sel = value.selection.normalized() val sel = value.selection.normalized()
snapshot()
if (!sel.collapsed) applyStyleToSelection(style) if (!sel.collapsed) applyStyleToSelection(style)
else toggleTypingStyle(style) else toggleTypingStyle(style)