@@ -105,13 +105,15 @@ class TextEditorActivity : AppCompatActivity() {
105105 private val progressBarDialog by lazy { ProgressBarDialog (this ) }
106106
107107 // --- Undo / Redo ---
108- // Lưu lại toàn bộ nội dung tại mỗi "đợt" chỉnh sửa (gộp các ký tự gõ liên tục thành một
109- // bước, thay vì lưu theo từng ký tự) để nút Undo/Redo hoạt động tự nhiên như trình soạn
110- // thảo thông thường. EditText mặc định của Android không có sẵn undo/redo công khai nên
111- // phải tự quản lý 2 stack này.
112- private val undoStack = ArrayDeque <String >()
113- private val redoStack = ArrayDeque <String >()
114- private var pendingUndoSnapshot: String? = null
108+ // Lưu lại toàn bộ nội dung và vị trí con trỏ tại mỗi "đợt" chỉnh sửa (gộp các ký tự gõ
109+ // liên tục thành một bước, thay vì lưu theo từng ký tự) để nút Undo/Redo hoạt động tự nhiên
110+ // như trình soạn thảo thông thường. EditText mặc định của Android không có sẵn undo/redo
111+ // công khai nên phải tự quản lý 2 stack này.
112+ private data class EditorSnapshot (val text : String , val cursor : Int )
113+
114+ private val undoStack = ArrayDeque <EditorSnapshot >()
115+ private val redoStack = ArrayDeque <EditorSnapshot >()
116+ private var pendingUndoSnapshot: EditorSnapshot ? = null
115117 private var isApplyingHistory = false
116118 private var undoButton: ImageButton ? = null
117119 private var redoButton: ImageButton ? = null
@@ -127,6 +129,7 @@ class TextEditorActivity : AppCompatActivity() {
127129
128130 val toolbar = findViewById<View >(R .id.toolbar) as Toolbar
129131 setSupportActionBar(toolbar)
132+ supportActionBar?.setDisplayShowTitleEnabled(false )
130133 // Bỏ nút back (mũi tên) trên toolbar theo yêu cầu — việc thoát trang giờ thực hiện qua
131134 // mục "Thoát" trong menu (xem onOptionsItemSelected). Nút back cứng/gesture của hệ
132135 // thống vẫn hoạt động bình thường nhờ onBackPressedDispatcher bên dưới.
@@ -308,7 +311,10 @@ class TextEditorActivity : AppCompatActivity() {
308311 override fun beforeTextChanged (s : CharSequence? , start : Int , count : Int , after : Int ) {
309312 if (isApplyingHistory) return
310313 if (pendingUndoSnapshot == null ) {
311- pendingUndoSnapshot = s?.toString().orEmpty()
314+ // Chụp nội dung VÀ vị trí con trỏ ngay trước khi thay đổi xảy ra —
315+ // đây là trạng thái cần quay lại khi người dùng bấm Undo.
316+ val cursor = binding.editorContent.selectionStart.coerceAtLeast(0 )
317+ pendingUndoSnapshot = EditorSnapshot (s?.toString().orEmpty(), cursor)
312318 }
313319 }
314320
@@ -328,7 +334,7 @@ class TextEditorActivity : AppCompatActivity() {
328334 val snapshot = pendingUndoSnapshot ? : return
329335 pendingUndoSnapshot = null
330336 val current = binding.editorContent.text?.toString().orEmpty()
331- if (snapshot == current) return
337+ if (snapshot.text == current) return
332338
333339 if (undoStack.size >= UNDO_HISTORY_LIMIT ) {
334340 undoStack.removeFirst()
@@ -339,9 +345,10 @@ class TextEditorActivity : AppCompatActivity() {
339345
340346 private fun performUndo () {
341347 undoHandler.removeCallbacks(commitPendingUndoRunnable)
342- val current = binding.editorContent.text?.toString().orEmpty()
348+ val currentText = binding.editorContent.text?.toString().orEmpty()
349+ val currentCursor = binding.editorContent.selectionStart.coerceAtLeast(0 )
343350
344- val target: String
351+ val target: EditorSnapshot
345352 val pending = pendingUndoSnapshot
346353 if (pending != null ) {
347354 // Đang có một đợt gõ chưa kịp "chốt" vào undoStack — hoàn tác thẳng về mốc đó.
@@ -352,8 +359,8 @@ class TextEditorActivity : AppCompatActivity() {
352359 target = undoStack.removeLast()
353360 }
354361
355- redoStack.addLast(current )
356- applyHistoryText (target)
362+ redoStack.addLast(EditorSnapshot (currentText, currentCursor) )
363+ applyHistorySnapshot (target)
357364 refreshUndoRedoButtons()
358365 }
359366
@@ -362,22 +369,23 @@ class TextEditorActivity : AppCompatActivity() {
362369 undoHandler.removeCallbacks(commitPendingUndoRunnable)
363370 pendingUndoSnapshot = null
364371
365- val current = binding.editorContent.text?.toString().orEmpty()
372+ val currentText = binding.editorContent.text?.toString().orEmpty()
373+ val currentCursor = binding.editorContent.selectionStart.coerceAtLeast(0 )
366374 val target = redoStack.removeLast()
367375
368376 if (undoStack.size >= UNDO_HISTORY_LIMIT ) {
369377 undoStack.removeFirst()
370378 }
371- undoStack.addLast(current )
372- applyHistoryText (target)
379+ undoStack.addLast(EditorSnapshot (currentText, currentCursor) )
380+ applyHistorySnapshot (target)
373381 refreshUndoRedoButtons()
374382 }
375383
376- private fun applyHistoryText ( text : String ) {
384+ private fun applyHistorySnapshot ( snapshot : EditorSnapshot ) {
377385 isApplyingHistory = true
378386 val editText = binding.editorContent
379- editText.setText(text)
380- editText.setSelection(text.length.coerceAtLeast( 0 ))
387+ editText.setText(snapshot. text)
388+ editText.setSelection(snapshot.cursor.coerceIn( 0 , snapshot.text.length ))
381389 isApplyingHistory = false
382390 }
383391
0 commit comments