6.7.0 - Alpha19 - 降低代码编辑器保存失败率; 优化代码编辑器保存按钮状态逻辑
This commit is contained in:
@@ -13,12 +13,14 @@ import android.text.TextPaint
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.ActionMode
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.size
|
||||
import com.afollestad.materialdialogs.DialogAction
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
@@ -32,6 +34,7 @@ import org.autojs.autojs.pio.PFiles
|
||||
import org.autojs.autojs.storage.file.TmpScriptFiles
|
||||
import org.autojs.autojs.theme.widget.ThemeColorToolbar
|
||||
import org.autojs.autojs.ui.BaseActivity
|
||||
import org.autojs.autojs.ui.error.ErrorDialogActivity
|
||||
import org.autojs.autojs.ui.main.MainActivity
|
||||
import org.autojs.autojs.ui.main.scripts.EditableFileInfoDialogManager
|
||||
import org.autojs.autojs.util.DialogUtils
|
||||
@@ -48,8 +51,8 @@ import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by Stardust on Jan 29, 2017.
|
||||
* Modified by SuperMonster003 as of Jan 21, 2023.
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 12, 2026.
|
||||
* Modified by SuperMonster003 as of Feb 12, 2026.
|
||||
*/
|
||||
open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyActivity {
|
||||
|
||||
@@ -290,7 +293,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
if (mEditorView.isTextChanged) {
|
||||
if (mEditorView.saveStickyDirty) {
|
||||
showExitConfirmDialog()
|
||||
} else {
|
||||
finishAndRemoveFromRecents()
|
||||
@@ -305,6 +308,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun showExitConfirmDialog() {
|
||||
DialogUtils.buildAndShowAdaptive {
|
||||
MaterialDialog.Builder(this)
|
||||
@@ -312,17 +316,60 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
|
||||
.content(R.string.edit_exit_without_save_warn)
|
||||
.neutralText(R.string.dialog_button_back)
|
||||
.negativeText(R.string.text_exit_directly)
|
||||
.onNeutral { d, _ -> d.dismiss() }
|
||||
.negativeColorRes(R.color.dialog_button_caution)
|
||||
.onNegative { _, _ ->
|
||||
.onNegative { d, _ ->
|
||||
runCatching { d.dismiss() }
|
||||
finishAndRemoveFromRecents()
|
||||
}
|
||||
.positiveText(R.string.text_save_and_exit)
|
||||
.positiveColorRes(R.color.dialog_button_warn)
|
||||
.onPositive { _, _ ->
|
||||
mEditorView.saveFile()
|
||||
finishAndRemoveFromRecents()
|
||||
.onPositive { d, _ ->
|
||||
// Save is async, exit only after success.
|
||||
// zh-CN: 保存是异步的, 保存成功后再退出.
|
||||
d.apply {
|
||||
setCancelable(false)
|
||||
getActionButton(DialogAction.NEUTRAL).apply {
|
||||
isEnabled = false
|
||||
setTextColor(getColor(R.color.dialog_button_unavailable))
|
||||
}
|
||||
getActionButton(DialogAction.NEGATIVE).apply {
|
||||
isEnabled = false
|
||||
setTextColor(getColor(R.color.dialog_button_unavailable))
|
||||
}
|
||||
getActionButton(DialogAction.POSITIVE).apply {
|
||||
isEnabled = false
|
||||
setTextColor(getColor(R.color.dialog_button_unavailable))
|
||||
}
|
||||
contentView?.postDelayed({
|
||||
contentView?.text = getString(R.string.text_saving)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
mEditorView
|
||||
.save()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
runCatching { d.dismiss() }
|
||||
finishAndRemoveFromRecents()
|
||||
}, { e: Throwable ->
|
||||
// Save failed, keep editor open.
|
||||
// zh-CN: 保存失败, 保持编辑器不退出.
|
||||
e.printStackTrace()
|
||||
runCatching { d.dismiss() }
|
||||
ErrorDialogActivity.showErrorDialog(this@EditActivity, R.string.error_failed_to_save, e.message)
|
||||
})
|
||||
}
|
||||
.autoDismiss(false)
|
||||
.build()
|
||||
.apply {
|
||||
contentView?.apply {
|
||||
setLineSpacing(0f, 1.2f)
|
||||
setLines(2)
|
||||
minLines = 2
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ import java.util.regex.Pattern
|
||||
* Created by Stardust on Sep 28, 2017.
|
||||
* Transformed by SuperMonster003 on May 1, 2023.
|
||||
* Modified by SuperMonster003 as of Feb 3, 2026.
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 12, 2026.
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener {
|
||||
@@ -135,6 +135,46 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
|
||||
private var _name: String? = null
|
||||
|
||||
// Sticky save dirty flag.
|
||||
// Behavior:
|
||||
// - Set to true on ANY text change (including undo/redo).
|
||||
// - Reset to false only after successful save or when a new baseline text is loaded.
|
||||
//
|
||||
// zh-CN:
|
||||
// 保存按钮的 sticky 脏标记.
|
||||
// 行为:
|
||||
// - 任意文本变化 (包括 undo/redo) 都会置为 true.
|
||||
// - 仅在保存成功或加载新基线文本时重置为 false.
|
||||
@Volatile
|
||||
var saveStickyDirty: Boolean = false
|
||||
|
||||
// Whether we have had any direct edits since last save/baseline.
|
||||
// Direct edits mean changes NOT caused by undo/redo buttons.
|
||||
//
|
||||
// zh-CN:
|
||||
// 自上次保存/建立基线以来是否发生过任何直接编辑.
|
||||
// 直接编辑指不是由撤销/重做按钮触发的文本变化.
|
||||
@Volatile
|
||||
private var mHadDirectEditSinceSave: Boolean = false
|
||||
|
||||
// Guard flag to mark text changes caused by undo/redo button actions.
|
||||
// This is used to distinguish direct edits from history navigation.
|
||||
//
|
||||
// zh-CN:
|
||||
// 用于标记由撤销/重做按钮动作导致的文本变化的哨兵标记.
|
||||
// 用于区分直接编辑与历史导航.
|
||||
@Volatile
|
||||
private var mUndoRedoButtonInProgress: Boolean = false
|
||||
|
||||
// Whether user has touched/moved caret during large file loading.
|
||||
// If true, we should NOT force caret to 0 on load completion.
|
||||
//
|
||||
// zh-CN:
|
||||
// 用户是否在大文件加载期间触摸过/移动过光标.
|
||||
// 若为 true, 则加载完成时不要强制把光标设为 0.
|
||||
@Volatile
|
||||
private var mUserMovedCursorDuringLoading: Boolean = false
|
||||
|
||||
var name: String
|
||||
get() = _name ?: "[ ${this.context.getString(text_unknown)} ]"
|
||||
set(value) {
|
||||
@@ -232,7 +272,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
|
||||
// Delay showing loading bar to avoid flicker.
|
||||
// zh-CN: 延迟显示加载提示条, 避免闪烁.
|
||||
private val mLoadingBarShowDelayMs = 1500L
|
||||
private val mLoadingBarShowDelayMs = 300L
|
||||
|
||||
// Once shown, keep it visible for at least this duration.
|
||||
// zh-CN: 一旦显示, 则保证最短展示时间.
|
||||
@@ -333,6 +373,8 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
// Initialize menu state early to avoid "first render" flicker.
|
||||
// zh-CN: 尽早初始化菜单状态, 避免首次渲染闪烁.
|
||||
mEditorLoading = true
|
||||
saveStickyDirty = false
|
||||
mHadDirectEditSinceSave = false
|
||||
syncPrimaryMenuState()
|
||||
|
||||
val name = intent.getStringExtra(EXTRA_NAME)
|
||||
@@ -851,12 +893,14 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
|
||||
mLargeFileMode = true
|
||||
mEditorLoading = true
|
||||
mUserMovedCursorDuringLoading = false
|
||||
syncPrimaryMenuState()
|
||||
|
||||
editText.setLoadingText(true)
|
||||
editor.setRedoUndoEnabled(false)
|
||||
editText.setLoadingGutterDigits(7)
|
||||
editText.setLoadingGutterDigits(3)
|
||||
editText.setText("")
|
||||
editText.setSelection(0)
|
||||
|
||||
val choreographer = Choreographer.getInstance()
|
||||
|
||||
@@ -901,8 +945,19 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
endEditorLoadingUi()
|
||||
|
||||
// Ensure caret/focus after loading is done.
|
||||
// zh-CN: 加载结束后确保光标/焦点.
|
||||
post { ensureEditorHasCursorAfterLoadIfNeeded() }
|
||||
// - Default: caret at 0 for "read from top".
|
||||
// - If user touched during loading: keep current caret (do not force to 0).
|
||||
//
|
||||
// zh-CN:
|
||||
// 加载结束后确保光标/焦点.
|
||||
// - 默认: 光标置于 0, 方便从头阅读.
|
||||
// - 若用户在加载期间触摸过: 保持当前光标, 不强制跳回 0.
|
||||
post {
|
||||
if (!mUserMovedCursorDuringLoading) {
|
||||
runCatching { editText.setSelection(0) }
|
||||
}
|
||||
ensureEditorHasCursorAfterLoadIfNeeded()
|
||||
}
|
||||
|
||||
mLargeFileFrameCallback = null
|
||||
return
|
||||
@@ -996,6 +1051,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
editor.markUndoRedoBaselineAsUnchanged()
|
||||
editor.setRedoUndoEnabled(!mLargeFileMode)
|
||||
|
||||
// Reset sticky dirty because we just established a new baseline.
|
||||
// zh-CN: 因刚刚建立了新的基线, 重置 sticky 脏标记.
|
||||
saveStickyDirty = false
|
||||
mHadDirectEditSinceSave = false
|
||||
syncPrimaryMenuState()
|
||||
|
||||
// Refresh highlight for restored text if size allows.
|
||||
// zh-CN: 若大小允许, 则对恢复文本刷新一次高亮.
|
||||
post { editor.refreshHighlightTokensIfAllowed() }
|
||||
@@ -1016,11 +1077,19 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
try {
|
||||
editText.setText(text)
|
||||
editor.markUndoRedoBaselineAsUnchanged()
|
||||
|
||||
// Reset sticky dirty because we just established a new baseline.
|
||||
// zh-CN: 因刚刚建立了新的基线, 重置 sticky 脏标记.
|
||||
saveStickyDirty = false
|
||||
mHadDirectEditSinceSave = false
|
||||
} finally {
|
||||
editor.setRedoUndoEnabled(true)
|
||||
editText.setLoadingText(false)
|
||||
}
|
||||
|
||||
mEditorLoading = false
|
||||
syncPrimaryMenuState()
|
||||
|
||||
// Ensure caret/focus after fast load.
|
||||
// zh-CN: 快速加载完成后确保光标/焦点.
|
||||
post { ensureEditorHasCursorAfterLoadIfNeeded() }
|
||||
@@ -1048,7 +1117,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
setMenuItemStatus(R.id.run, true)
|
||||
setMenuItemStatus(R.id.undo, false)
|
||||
setMenuItemStatus(R.id.redo, false)
|
||||
setMenuItemStatus(R.id.save, editor.isTextChanged)
|
||||
setMenuItemStatus(R.id.save, saveStickyDirty)
|
||||
}
|
||||
else -> {
|
||||
// Normal mode.
|
||||
@@ -1056,7 +1125,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
setMenuItemStatus(R.id.run, true)
|
||||
setMenuItemStatus(R.id.undo, editor.canUndo())
|
||||
setMenuItemStatus(R.id.redo, editor.canRedo())
|
||||
setMenuItemStatus(R.id.save, editor.isTextChanged)
|
||||
setMenuItemStatus(R.id.save, saveStickyDirty)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1224,12 +1293,40 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
private fun setUpEditor() {
|
||||
editor.let { editor ->
|
||||
editor.codeEditText.let { editText ->
|
||||
// Observe user touch in text area to preserve caret position after large-file load.
|
||||
// zh-CN: 监听用户在文本区域的触摸, 以便大文件加载完成后保留光标位置.
|
||||
editText.onUserTouchInTextArea = {
|
||||
if (mEditorLoading && mLargeFileMode) {
|
||||
mUserMovedCursorDuringLoading = true
|
||||
}
|
||||
}
|
||||
|
||||
editText.addTextChangedListener(SimpleTextWatcher { _ ->
|
||||
// Skip menu state updates during progressive loading.
|
||||
// zh-CN: 渐进式加载期间跳过菜单状态更新.
|
||||
if (editText.isLoadingText() || mEditorLoading) {
|
||||
return@SimpleTextWatcher
|
||||
}
|
||||
|
||||
// If this change is not caused by undo/redo buttons, treat it as a direct edit.
|
||||
// Direct edits keep Save sticky until next successful save.
|
||||
//
|
||||
// zh-CN:
|
||||
// 若本次变化并非由撤销/重做按钮触发, 则视为直接编辑.
|
||||
// 直接编辑会使保存按钮保持 sticky, 直到下一次保存成功.
|
||||
if (!mUndoRedoButtonInProgress) {
|
||||
mHadDirectEditSinceSave = true
|
||||
saveStickyDirty = true
|
||||
} else {
|
||||
// Undo/redo navigation:
|
||||
// Save should reflect (baseline changed) OR (there has been any direct edit since save).
|
||||
//
|
||||
// zh-CN:
|
||||
// undo/redo 历史导航:
|
||||
// 保存按钮应反映 (相对基线有变化) 或 (自保存以来发生过直接编辑).
|
||||
saveStickyDirty = editor.isTextChanged || mHadDirectEditSinceSave
|
||||
}
|
||||
|
||||
syncPrimaryMenuState()
|
||||
})
|
||||
|
||||
@@ -1268,6 +1365,8 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
// zh-CN: 初始化为 "新建文件" 预期状态: 仅运行可用.
|
||||
mEditorLoading = false
|
||||
mLargeFileMode = false
|
||||
saveStickyDirty = false
|
||||
mHadDirectEditSinceSave = false
|
||||
setMenuItemStatus(R.id.run, true)
|
||||
setMenuItemStatus(R.id.undo, false)
|
||||
setMenuItemStatus(R.id.redo, false)
|
||||
@@ -1287,7 +1386,9 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
val imeBarBackgroundColor = it.imeBarBackgroundColor
|
||||
|
||||
editor.setTheme(it)
|
||||
|
||||
mInputMethodEnhanceBar.setBackgroundColor(imeBarBackgroundColor)
|
||||
mLoadingBarContainer.setBackgroundColor(imeBarBackgroundColor)
|
||||
|
||||
run {
|
||||
val adjustedImageContrastColor = ColorUtils.adjustColorForContrast(imeBarBackgroundColor, appThemeColor, 3.6)
|
||||
@@ -1390,9 +1491,43 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
return run(true, file, uri?.path)
|
||||
}
|
||||
|
||||
private fun undo() = editor.undo()
|
||||
private fun undo() {
|
||||
// Mark undo/redo button action so TextWatcher can distinguish it.
|
||||
// zh-CN: 标记撤销/重做按钮动作, 以便 TextWatcher 区分来源.
|
||||
mUndoRedoButtonInProgress = true
|
||||
try {
|
||||
editor.undo()
|
||||
} finally {
|
||||
mUndoRedoButtonInProgress = false
|
||||
}
|
||||
|
||||
private fun redo() = editor.redo()
|
||||
// Recompute Save state after history navigation.
|
||||
// zh-CN: 历史导航后重新计算保存按钮状态.
|
||||
saveStickyDirty = editor.isTextChanged || mHadDirectEditSinceSave
|
||||
syncPrimaryMenuState()
|
||||
}
|
||||
|
||||
private fun redo() {
|
||||
// Mark undo/redo button action so TextWatcher can distinguish it.
|
||||
// zh-CN: 标记撤销/重做按钮动作, 以便 TextWatcher 区分来源.
|
||||
mUndoRedoButtonInProgress = true
|
||||
try {
|
||||
editor.redo()
|
||||
} finally {
|
||||
mUndoRedoButtonInProgress = false
|
||||
}
|
||||
|
||||
// Recompute Save state after history navigation.
|
||||
// This makes "edit -> save -> undo -> redo" turn Save off again,
|
||||
// because we are back to baseline and there was no direct edit since save.
|
||||
//
|
||||
// zh-CN:
|
||||
// 历史导航后重新计算保存按钮状态.
|
||||
// 这会使 "编辑 -> 保存 -> 撤销 -> 重做" 再次熄灭保存按钮,
|
||||
// 因为已回到基线, 且保存后没有发生直接编辑.
|
||||
saveStickyDirty = editor.isTextChanged || mHadDirectEditSinceSave
|
||||
syncPrimaryMenuState()
|
||||
}
|
||||
|
||||
fun save(): Observable<String> =
|
||||
Observable
|
||||
@@ -1414,6 +1549,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext {
|
||||
editor.markTextAsSaved()
|
||||
|
||||
// Reset sticky dirty only on successful save.
|
||||
// zh-CN: 仅在保存成功后重置 sticky 脏标记.
|
||||
saveStickyDirty = false
|
||||
mHadDirectEditSinceSave = false
|
||||
|
||||
setMenuItemStatus(R.id.save, false)
|
||||
}
|
||||
|
||||
@@ -1695,9 +1836,13 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
|
||||
uri = uri,
|
||||
onRestoreToEditor = { restoredText ->
|
||||
editor.text = restoredText
|
||||
|
||||
// Restoring changes content => mark sticky dirty.
|
||||
// zh-CN: 恢复版本会改变内容, 因此置 sticky 脏标记.
|
||||
saveStickyDirty = true
|
||||
},
|
||||
onRestoredUi = {
|
||||
setMenuItemStatus(R.id.save, true)
|
||||
syncPrimaryMenuState()
|
||||
showSnack(this@EditorView, R.string.text_done)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -83,6 +83,15 @@ class CodeEditText : AppCompatEditText {
|
||||
private var mDebuggingLine = -1
|
||||
private var mCursorChangeCallbacks: CopyOnWriteArrayList<CursorChangeCallback>? = null
|
||||
|
||||
// Callback when user touches the editor text area (not gutter).
|
||||
// This is used by large-file loader to avoid forcing caret to 0 after user interaction.
|
||||
//
|
||||
// zh-CN:
|
||||
// 当用户触摸编辑器文本区域 (非行号区域) 时的回调.
|
||||
// 用于大文件加载逻辑: 若用户已交互, 则避免加载完成后强制将光标跳到 0.
|
||||
@Volatile
|
||||
var onUserTouchInTextArea: (() -> Unit)? = null
|
||||
|
||||
// Read-only state.
|
||||
// zh-CN: 只读状态.
|
||||
private var mReadOnly = false
|
||||
@@ -649,6 +658,12 @@ class CodeEditText : AppCompatEditText {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
// Notify outer layer early when user touches inside text area (not gutter).
|
||||
// zh-CN: 当用户触摸文本区域 (非行号区域) 时尽早通知外层.
|
||||
if (event.action == MotionEvent.ACTION_DOWN && event.x >= paddingLeft) {
|
||||
onUserTouchInTextArea?.invoke()
|
||||
}
|
||||
|
||||
// 如果行号区域被按下
|
||||
if (event.action == MotionEvent.ACTION_DOWN && event.x < paddingLeft) {
|
||||
// 则计算当前行, 如果行号有效, 记录起来
|
||||
|
||||
@@ -4,7 +4,6 @@ package org.autojs.autojs.ui.edit.editor;
|
||||
* THIS CLASS IS PROVIDED TO THE PUBLIC DOMAIN FOR FREE WITHOUT ANY
|
||||
* RESTRICTIONS OR ANY WARRANTY.
|
||||
*/
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
@@ -15,10 +14,12 @@ import android.text.TextWatcher;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A generic undo/redo implementation for TextViews.
|
||||
*
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||
* <p>
|
||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 12, 2026.
|
||||
*/
|
||||
public class TextViewUndoRedo {
|
||||
|
||||
@@ -48,6 +49,10 @@ public class TextViewUndoRedo {
|
||||
|
||||
private int mInitialHistoryStackSize;
|
||||
|
||||
// Baseline position for "unchanged" state.
|
||||
// zh-CN: "未修改" 状态的基线位置.
|
||||
private int mInitialHistoryPosition;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
private int mTextChangeId = 0;
|
||||
private final boolean mTextChanging = false;
|
||||
@@ -76,14 +81,14 @@ public class TextViewUndoRedo {
|
||||
|
||||
/**
|
||||
* Reset undo/redo history without modifying the current text.
|
||||
*
|
||||
* <p>
|
||||
* This is useful after bulk loading text progressively, where the text is already in the TextView
|
||||
* and we only want to treat it as the "baseline" (unchanged) state.
|
||||
*
|
||||
* <p>
|
||||
* zh-CN:
|
||||
*
|
||||
* <p>
|
||||
* 在不修改当前文本的前提下重置 undo/redo 历史.
|
||||
*
|
||||
* <p>
|
||||
* 适用于渐进式批量加载文本后: 文本已经在 TextView 中, 此时只需要把它视为 "基线" (未修改) 状态.
|
||||
*/
|
||||
public final void resetHistoryAsUnchanged() {
|
||||
@@ -110,12 +115,21 @@ public class TextViewUndoRedo {
|
||||
mIsUndoOrRedo = false;
|
||||
}
|
||||
|
||||
public boolean isTextChanged(){
|
||||
return mInitialHistoryStackSize != mEditHistory.size();
|
||||
public boolean isTextChanged() {
|
||||
// IMPORTANT:
|
||||
// Use history position rather than history size.
|
||||
// Undo/redo changes mmPosition while keeping history size the same.
|
||||
//
|
||||
// zh-CN:
|
||||
// 重要:
|
||||
// 使用 history position 而不是 history size.
|
||||
// undo/redo 会改变 mmPosition, 但 history size 往往不变.
|
||||
return mInitialHistoryPosition != mEditHistory.mmPosition;
|
||||
}
|
||||
|
||||
public void markTextAsUnchanged() {
|
||||
mInitialHistoryStackSize = mEditHistory.size();
|
||||
mInitialHistoryPosition = mEditHistory.mmPosition;
|
||||
}
|
||||
|
||||
// =================================================================== //
|
||||
@@ -141,6 +155,7 @@ public class TextViewUndoRedo {
|
||||
public void clearHistory() {
|
||||
mEditHistory.clear();
|
||||
mInitialHistoryStackSize = 0;
|
||||
mInitialHistoryPosition = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,7 +354,7 @@ public class TextViewUndoRedo {
|
||||
}
|
||||
}
|
||||
|
||||
public int size(){
|
||||
public int size() {
|
||||
return mmHistory.size();
|
||||
}
|
||||
|
||||
@@ -446,7 +461,7 @@ public class TextViewUndoRedo {
|
||||
mTextChangeId++;
|
||||
mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange));
|
||||
int textChangeId = mTextChangeId;
|
||||
//TODO 增加连续输入文字当成一次撤销的功能
|
||||
// TODO 增加连续输入文字当成一次撤销的功能
|
||||
}
|
||||
|
||||
public void afterTextChanged(Editable s) {
|
||||
@@ -456,6 +471,15 @@ public class TextViewUndoRedo {
|
||||
if (mEditHistory.size() < mInitialHistoryStackSize) {
|
||||
mInitialHistoryStackSize = 0;
|
||||
}
|
||||
|
||||
// Keep baseline position within valid range after trims.
|
||||
// zh-CN: 在 trim 等场景后, 保持基线 position 落在合法范围内.
|
||||
if (mInitialHistoryPosition > mEditHistory.mmHistory.size()) {
|
||||
mInitialHistoryPosition = mEditHistory.mmHistory.size();
|
||||
}
|
||||
if (mInitialHistoryPosition < 0) {
|
||||
mInitialHistoryPosition = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1331,4 +1331,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">النوافذ المنبثقة في الخلفية</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">لم يتم حفظ الاعدادات. هل تريد المتابعة?</string>
|
||||
<string name="warn_exit_without_saving_settings">لم يتم حفظ الاعدادات. هل تريد الخروج?</string>
|
||||
<string name="error_failed_to_save">فشل الحفظ</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">جارٍ الحفظ...</string>
|
||||
</resources>
|
||||
@@ -1326,4 +1326,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">The settings has not been saved, are you sure to continue the operation?</string>
|
||||
<string name="warn_exit_without_saving_settings">The settings has not been saved, are you sure to exit?</string>
|
||||
<string name="error_failed_to_save">Failed to save</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">Saving...</string>
|
||||
</resources>
|
||||
@@ -1329,4 +1329,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">Ventanas emergentes en segundo plano</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">La configuracion no se ha guardado. ¿Seguro que quieres continuar?</string>
|
||||
<string name="warn_exit_without_saving_settings">La configuracion no se ha guardado. ¿Seguro que quieres salir?</string>
|
||||
<string name="error_failed_to_save">Error al guardar</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">Guardando...</string>
|
||||
</resources>
|
||||
@@ -1329,4 +1329,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">Fenêtres contextuelles en arrière-plan</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">Les parametres ne sont pas enregistres. Continuer?</string>
|
||||
<string name="warn_exit_without_saving_settings">Les parametres ne sont pas enregistres. Quitter?</string>
|
||||
<string name="error_failed_to_save">Echec de l\'enregistrement</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">Enregistrement...</string>
|
||||
</resources>
|
||||
@@ -1330,4 +1330,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">バックグラウンドでのポップアップ表示</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">設定が保存されていません. 続行しますか?</string>
|
||||
<string name="warn_exit_without_saving_settings">設定が保存されていません. 終了しますか?</string>
|
||||
<string name="error_failed_to_save">保存に失敗しました</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">保存中...</string>
|
||||
</resources>
|
||||
@@ -1331,4 +1331,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">백그라운드 팝업</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">설정이 저장되지 않았습니다. 계속할까요?</string>
|
||||
<string name="warn_exit_without_saving_settings">설정이 저장되지 않았습니다. 종료할까요?</string>
|
||||
<string name="error_failed_to_save">저장에 실패했습니다</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">저장 중...</string>
|
||||
</resources>
|
||||
@@ -1329,4 +1329,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">Всплывающие окна в фоне</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">Настройки не сохранены. Продолжить?</string>
|
||||
<string name="warn_exit_without_saving_settings">Настройки не сохранены. Выйти?</string>
|
||||
<string name="error_failed_to_save">Не удалось сохранить</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">Сохранение...</string>
|
||||
</resources>
|
||||
@@ -1325,4 +1325,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">後台彈出界面</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">設置尚未保存, 確定要繼續操作嗎</string>
|
||||
<string name="warn_exit_without_saving_settings">設置尚未保存, 確定要退出嗎</string>
|
||||
<string name="error_failed_to_save">保存失敗</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">正在保存...</string>
|
||||
</resources>
|
||||
@@ -1325,4 +1325,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">後臺彈出介面</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">設定尚未儲存, 確定要繼續操作嗎</string>
|
||||
<string name="warn_exit_without_saving_settings">設定尚未儲存, 確定要退出嗎</string>
|
||||
<string name="error_failed_to_save">儲存失敗</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">正在儲存...</string>
|
||||
</resources>
|
||||
@@ -1326,4 +1326,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">设置尚未保存, 确定要继续操作吗</string>
|
||||
<string name="warn_exit_without_saving_settings">设置尚未保存, 确定要退出吗</string>
|
||||
<string name="error_failed_to_save">保存失败</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">正在保存...</string>
|
||||
</resources>
|
||||
@@ -1600,4 +1600,6 @@
|
||||
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
|
||||
<string name="warn_continue_operation_without_saving_settings">The settings has not been saved, are you sure to continue the operation?</string>
|
||||
<string name="warn_exit_without_saving_settings">The settings has not been saved, are you sure to exit?</string>
|
||||
<string name="error_failed_to_save">Failed to save</string>
|
||||
<string name="text_saving" tools:ignore="TypographyEllipsis">Saving...</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user