From 9715d958ae2a439b55a08115411397f545aa7c4d Mon Sep 17 00:00:00 2001 From: Raihan Ariq <202310715297@mhs.ubharajaya.ac.id> Date: Thu, 25 Dec 2025 17:25:33 +0700 Subject: [PATCH] Fitur undo redo --- .../note/EditableFullScreenNoteView.kt | 11 ++++- .../DraggableMiniMarkdownToolbar.kt | 41 +++++++++++++++++-- .../screens/note/editor/RichEditorState.kt | 29 +++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/notesai/presentation/screens/note/EditableFullScreenNoteView.kt b/app/src/main/java/com/example/notesai/presentation/screens/note/EditableFullScreenNoteView.kt index bd14db9..225db08 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/note/EditableFullScreenNoteView.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/note/EditableFullScreenNoteView.kt @@ -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() } ) } diff --git a/app/src/main/java/com/example/notesai/presentation/screens/note/components/DraggableMiniMarkdownToolbar.kt b/app/src/main/java/com/example/notesai/presentation/screens/note/components/DraggableMiniMarkdownToolbar.kt index a845653..d291451 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/note/components/DraggableMiniMarkdownToolbar.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/note/components/DraggableMiniMarkdownToolbar.kt @@ -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) ) } diff --git a/app/src/main/java/com/example/notesai/presentation/screens/note/editor/RichEditorState.kt b/app/src/main/java/com/example/notesai/presentation/screens/note/editor/RichEditorState.kt index 9d01124..edbaa7f 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/note/editor/RichEditorState.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/note/editor/RichEditorState.kt @@ -17,6 +17,32 @@ class RichEditorState(initial: AnnotatedString) { ) ) + /* ===================== + UNDO / REDO + ===================== */ + private val undoStack = mutableStateListOf() + private val redoStack = mutableStateListOf() + + 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)