6.7.0 - Alpha21 - 修复代码编辑器可能导致未保存内容被重置的问题 (试修)

This commit is contained in:
SuperMonster003
2026-02-25 03:04:22 +08:00
parent e5c4e9ac8c
commit 3e8f70de24
3 changed files with 118 additions and 25 deletions

View File

@@ -110,7 +110,32 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
EditorMenu(editorView, readOnly).also { EditorMenu(editorView, readOnly).also {
mEditorMenu = it mEditorMenu = it
} }
StableDraftFileHelper(this, editorView.uri?.path).also {
// Restore draft as early as possible to avoid being overwritten by async file loading.
// We intentionally do this in onCreate(), not in onRestoreInstanceState(),
// because handleIntent() may start async loading and call setInitialText() later.
//
// zh-CN:
// 尽可能早地恢复草稿, 避免被异步文件加载覆盖.
// 我们刻意在 onCreate() 中恢复, 而不是在 onRestoreInstanceState() 中恢复,
// 因为 handleIntent() 可能启动异步加载并在稍后调用 setInitialText().
savedInstanceState?.getString("text")?.let { draftText ->
mEditorView.restoreDraftTextForThisSession(draftText)
} ?: savedInstanceState?.getString("path")?.let { path ->
Observable.just(path)
.observeOn(Schedulers.io())
.map { PFiles.read(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ draftText ->
mEditorView.restoreDraftTextForThisSession(draftText)
}, Throwable::printStackTrace)
}
// Use a stable key from intent instead of editorView.uri (which is not set yet here).
// zh-CN: 使用 intent 中的稳定 key, 避免此处 editorView.uri 尚未赋值导致 key 为 null.
val draftKeyPath = intent.getStringExtra(EditorView.EXTRA_PATH) ?: intent.data?.path
StableDraftFileHelper(this, draftKeyPath).also {
draftFileHelper = it draftFileHelper = it
} }
(intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0).also { (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0).also {
@@ -403,9 +428,15 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
// Save draft when UI indicates "needs save".
// zh-CN: 当 UI 表示 "需要保存" 时保存草稿. // Save draft when content actually differs from baseline OR UI indicates "needs save".
if (!mEditorView.saveStickyDirty) { // This makes state restore robust against any sticky/menu state glitches.
//
// zh-CN:
// 当内容相对基线确实发生变化, 或 UI 表示 "需要保存" 时, 保存草稿.
// 这能使状态恢复不再受 sticky/menu 状态偶发异常的影响.
val needDraft = mEditorView.isTextChanged || mEditorView.saveStickyDirty
if (!needDraft) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
return return
} }
@@ -431,24 +462,10 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
mEditorView.syncPrimaryMenuState()
runCatching { mEditorView.refreshSymbolsBar() } runCatching { mEditorView.refreshSymbolsBar() }
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
savedInstanceState.getString("text")?.let {
mEditorView.setRestoredText(it)
return
}
savedInstanceState.getString("path")?.let { path ->
Observable.just(path)
.observeOn(Schedulers.io())
.map { PFiles.read(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ mEditorView.editor.text = it }, Throwable::printStackTrace)
}
}
override fun addRequestPermissionsCallback(callback: OnRequestPermissionsResultCallback) { override fun addRequestPermissionsCallback(callback: OnRequestPermissionsResultCallback) {
mRequestPermissionCallbacks.addCallback(callback) mRequestPermissionCallbacks.addCallback(callback)
} }

View File

@@ -158,6 +158,15 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
@Volatile @Volatile
private var mHadDirectEditSinceSave: Boolean = false private var mHadDirectEditSinceSave: Boolean = false
// Whether a draft has been restored in this session.
// If true, subsequent async file-load setInitialText() must NOT overwrite the restored draft.
//
// zh-CN:
// 本次会话是否已恢复草稿.
// 若为 true, 则后续异步文件加载触发的 setInitialText() 不得覆盖已恢复的草稿.
@Volatile
private var mDraftRestoredInThisSession: Boolean = false
// Guard flag to mark text changes caused by undo/redo button actions. // Guard flag to mark text changes caused by undo/redo button actions.
// This is used to distinguish direct edits from history navigation. // This is used to distinguish direct edits from history navigation.
// //
@@ -374,8 +383,20 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// Initialize menu state early to avoid "first render" flicker. // Initialize menu state early to avoid "first render" flicker.
// zh-CN: 尽早初始化菜单状态, 避免首次渲染闪烁. // zh-CN: 尽早初始化菜单状态, 避免首次渲染闪烁.
mEditorLoading = true mEditorLoading = true
// Do NOT reset dirty flags if we already restored a draft in this session.
// Otherwise, the editor will keep the draft text but lose "needs save" state,
// causing Save button to turn off and exit-confirm not to show.
//
// zh-CN:
// 若本会话已恢复草稿, 则不要重置脏标记.
// 否则会出现草稿文本仍在但 "需要保存" 状态丢失,
// 导致保存按钮熄灭且退出不提示保存.
if (!mDraftRestoredInThisSession) {
saveStickyDirty = false saveStickyDirty = false
mHadDirectEditSinceSave = false mHadDirectEditSinceSave = false
}
syncPrimaryMenuState() syncPrimaryMenuState()
val name = intent.getStringExtra(EXTRA_NAME) val name = intent.getStringExtra(EXTRA_NAME)
@@ -418,6 +439,49 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
editor.text = text editor.text = text
} }
/**
* Restore draft text for this editor session.
*
* Behavior:
* - Applies text immediately.
* - Marks current session as "draft restored", so async file loading won't overwrite it.
* - Marks editor state as dirty (needs save) and refreshes menu state.
*
* zh-CN:
* 为本次编辑会话恢复草稿文本.
*
* 行为:
* - 立即应用文本.
* - 标记本会话为 "已恢复草稿", 防止异步文件加载覆盖.
* - 标记为未保存并刷新菜单状态.
*/
fun restoreDraftTextForThisSession(text: String) {
mDraftRestoredInThisSession = true
mRestoredText = text
editor.text = text
markRestoredDraftAsDirty()
}
/**
* Mark a restored draft as unsaved and refresh menu state.
*
* Rationale:
* - Draft restore should not be treated as "saved baseline".
* - Otherwise Save button may appear disabled and exit-confirm may not show.
*
* zh-CN:
* 将已恢复的草稿标记为未保存, 并刷新菜单状态.
*
* 理由:
* - 草稿恢复不应被当作 "已保存基线".
* - 否则保存按钮可能呈灰色, 且退出确认可能不弹出.
*/
fun markRestoredDraftAsDirty() {
saveStickyDirty = true
mHadDirectEditSinceSave = true
syncPrimaryMenuState()
}
private fun handleText(intent: Intent): Observable<String> { private fun handleText(intent: Intent): Observable<String> {
val content = intent.getStringExtra(EXTRA_CONTENT) val content = intent.getStringExtra(EXTRA_CONTENT)
if (content != null) { if (content != null) {
@@ -1045,6 +1109,18 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
mEditorLoading = true mEditorLoading = true
syncPrimaryMenuState() syncPrimaryMenuState()
// If draft has been restored in this session, do NOT overwrite it with async file load results.
// Still end loading flags and sync menu state to keep UI consistent.
//
// zh-CN:
// 若本会话已恢复草稿, 则不要用异步文件加载结果覆盖它.
// 但仍需结束 loading 标记并同步菜单状态, 保持 UI 一致.
if (mDraftRestoredInThisSession) {
mEditorLoading = false
syncPrimaryMenuState()
return
}
if (mRestoredText != null) { if (mRestoredText != null) {
editor.text = mRestoredText!! editor.text = mRestoredText!!
mRestoredText = null mRestoredText = null
@@ -1104,7 +1180,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
} }
} }
private fun syncPrimaryMenuState() { internal fun syncPrimaryMenuState() {
// Unified primary buttons state. // Unified primary buttons state.
// zh-CN: 统一主按钮状态. // zh-CN: 统一主按钮状态.
when { when {

View File

@@ -1,5 +1,5 @@
#Mon Feb 23 16:51:52 CST 2026 #Wed Feb 25 03:02:22 CST 2026
BUILD_TIME=1771836712381 BUILD_TIME=1771959742388
COMPILE_SDK_VERSION=36 COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36 TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3750 VERSION_BUILD=3752
VERSION_NAME=6.7.0 Alpha21 VERSION_NAME=6.7.0 Alpha21
VSCODE_EXT_REQUIRED_VERSION=1.0.13 VSCODE_EXT_REQUIRED_VERSION=1.0.13