6.7.0 - Alpha19 - 版本历史 M1 - 代码编辑器保存文件失败时自动存为草稿并支持另存为新文件

This commit is contained in:
SuperMonster003
2026-02-03 15:48:23 +08:00
parent c3e5175a68
commit 6d964b7949
15 changed files with 391 additions and 46 deletions

View File

@@ -132,6 +132,7 @@
"控制台浮动窗口背景色彩行为相关 API (透明度/着色/基色) 更符合安卓设计规范 _[`issue #458`](http://issues.autojs6.com/458)_",
"文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机",
"文件管理器/任务面板支持显示文件/任务数量统计信息",
"代码编辑器保存文件失败时自动存为草稿并支持另存为新文件",
"打包应用页面默认勾选必要权限 (WAKE_LOCK/INTERNET/WRITE_EXTERNAL_STORAGE) _[`issue #397`](http://issues.autojs6.com/397)_",
"打包应用设置页面增加前台服务开关 _[`issue #406`](http://issues.autojs6.com/406)_",
"脚本项目配置文件保存时增加键名冲突检测机制防止键名歧义",

View File

@@ -92,7 +92,7 @@ object DialogUtils {
@JvmOverloads
@Deprecated("Use showAdaptive instead.", ReplaceWith("showAdaptive(dialog, focusable)"))
@ReservedForCompatibility
fun <T : MaterialDialog> showDialog(dialog: T, focusable: Boolean = true): T {
fun showDialog(dialog: MaterialDialog, focusable: Boolean = true): MaterialDialog {
runOnMain {
// Prevent duplicated show.
// zh-CN: 防止重复 show().
@@ -205,9 +205,9 @@ object DialogUtils {
* - 在后台线程 build 可能触发 "Can't create handler inside thread ..." 崩溃.
*/
@JvmStatic
fun <T : MaterialDialog> buildAdaptive(builder: MaterialDialog.Builder): T {
fun buildAdaptive(builder: MaterialDialog.Builder): MaterialDialog {
@Suppress("UNCHECKED_CAST")
return buildAdaptive { builder.build() as T }
return buildAdaptive { builder.build() }
}
/**
@@ -216,12 +216,12 @@ object DialogUtils {
* zh-CN: 通过 callable 工厂在主线程 build 对话框.
*/
@JvmStatic
fun <T : MaterialDialog> buildAdaptive(factory: Callable<T>): T {
fun buildAdaptive(factory: Callable<MaterialDialog>): MaterialDialog {
if (Looper.getMainLooper() == Looper.myLooper()) {
return factory.call()
}
val ref = AtomicReference<T>()
val ref = AtomicReference<MaterialDialog>()
val err = AtomicReference<Throwable?>()
val latch = CountDownLatch(1)
@@ -256,8 +256,8 @@ object DialogUtils {
*/
@JvmStatic
@JvmOverloads
fun <T : MaterialDialog> buildAndShowAdaptive(builder: MaterialDialog.Builder, focusable: Boolean = true): T {
val dialog = buildAdaptive<T>(builder)
fun buildAndShowAdaptive(builder: MaterialDialog.Builder, focusable: Boolean = true): MaterialDialog {
val dialog = buildAdaptive(builder)
@Suppress("DEPRECATION")
return showDialog(dialog, focusable)
}
@@ -269,7 +269,7 @@ object DialogUtils {
*/
@JvmStatic
@JvmOverloads
fun <T : MaterialDialog> buildAndShowAdaptive(factory: Callable<T>, focusable: Boolean = true): T {
fun buildAndShowAdaptive(factory: Callable<MaterialDialog>, focusable: Boolean = true): MaterialDialog {
val dialog = buildAdaptive(factory)
@Suppress("DEPRECATION")
return showDialog(dialog, focusable)
@@ -284,7 +284,7 @@ object DialogUtils {
}
@JvmStatic
fun <T : MaterialDialog> fixCheckBoxGravity(dialog: T): T = dialog.also {
fun fixCheckBoxGravity(dialog: MaterialDialog): MaterialDialog = dialog.also {
it.view.findViewById<CheckBox>(com.afollestad.materialdialogs.R.id.md_promptCheckbox)?.gravity = Gravity.CENTER_VERTICAL
}

View File

@@ -13,7 +13,6 @@ import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.util.SparseBooleanArray
import android.view.View
import android.widget.ImageView
@@ -27,13 +26,13 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.autojs.autojs.AutoJs
import org.autojs.autojs.app.DialogUtils
import org.autojs.autojs.core.pref.Pref.getEditorTextSize
import org.autojs.autojs.core.pref.Pref.setEditorTextSize
import org.autojs.autojs.engine.JavaScriptEngine
import org.autojs.autojs.engine.ScriptEngine
import org.autojs.autojs.event.BackPressedHandler.HostActivity
import org.autojs.autojs.execution.ScriptExecution
import org.autojs.autojs.util.MaterialDialogUtils.choiceWidgetThemeColor
import org.autojs.autojs.model.autocomplete.AutoCompletion
import org.autojs.autojs.model.autocomplete.CodeCompletions
import org.autojs.autojs.model.autocomplete.Symbols
@@ -46,7 +45,6 @@ import org.autojs.autojs.model.script.Scripts.EXTRA_EXCEPTION_MESSAGE
import org.autojs.autojs.model.script.Scripts.openByOtherApps
import org.autojs.autojs.model.script.Scripts.runWithBroadcastSender
import org.autojs.autojs.pio.PFiles.getNameWithoutExtension
import org.autojs.autojs.pio.PFiles.move
import org.autojs.autojs.pio.PFiles.write
import org.autojs.autojs.storage.file.TmpScriptFiles
import org.autojs.autojs.tool.Callback
@@ -65,11 +63,14 @@ import org.autojs.autojs.ui.edit.toolbar.DebugToolbarFragment
import org.autojs.autojs.ui.edit.toolbar.NormalToolbarFragment
import org.autojs.autojs.ui.edit.toolbar.SearchToolbarFragment
import org.autojs.autojs.ui.edit.toolbar.ToolbarFragment
import org.autojs.autojs.ui.filechooser.FileChooserDialogBuilder
import org.autojs.autojs.ui.log.LogActivity
import org.autojs.autojs.ui.widget.EWebView
import org.autojs.autojs.ui.widget.SimpleTextWatcher
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.DisplayUtils.pxToSp
import org.autojs.autojs.util.DocsUtils.getUrl
import org.autojs.autojs.util.MaterialDialogUtils.choiceWidgetThemeColor
import org.autojs.autojs.util.Observers
import org.autojs.autojs.util.StringUtils
import org.autojs.autojs.util.ViewUtils.showSnack
@@ -80,11 +81,14 @@ import java.io.File
import java.io.IOException
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.Locale
/**
* Created by Stardust on Sep 28, 2017.
* Modified by SuperMonster003 as of May 1, 2023.
* Transformed by SuperMonster003 on May 1, 2023.
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 3, 2026.
* Modified by SuperMonster003 as of Feb 3, 2026.
*/
@SuppressLint("CheckResult")
class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener {
@@ -429,47 +433,331 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private fun redo() = editor.redo()
fun save(): Observable<String> {
val path = uri.path!!
val backPath = "$path.save"
move(path, backPath)
return Observable
fun save(): Observable<String> =
Observable
.fromCallable {
// Use a transactional save flow for both file:// and content://.
// zh-CN: 对 file:// 与 content:// 统一使用事务式保存流程.
editor.text.apply {
writeTextWithCharset(uri, this)
writeTextWithCharsetTransactional(uri, this)
}
}
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
editor.markTextAsSaved()
setMenuItemStatus(R.id.save, false)
}
.doOnNext {
if (!File(backPath).delete()) {
Log.e(TAG, "save: failed")
}
/**
* Transactional save for text files.
*
* Behavior:
* 1) SAVE_PRE: read old bytes (best-effort).
* 2) Build new bytes with charset/BOM strategy.
* 3) Write with openOutputStream(uri, "rwt").
* 4) Verify by reading back and comparing hash.
* 5) On failure:
* - Try rollback to SAVE_PRE when possible.
* - Always save an emergency draft, then prompt user to "Save as".
*
* zh-CN:
*
* 面向文本文件的事务式保存.
*
* 行为:
* 1) SAVE_PRE: 尽力读取旧 bytes.
* 2) 按 charset/BOM 策略生成新 bytes.
* 3) 通过 openOutputStream(uri, "rwt") 写入.
* 4) 写后读回并比对 hash 校验.
* 5) 失败时:
* - 在可能时回滚到 SAVE_PRE.
* - 无论能否回滚, 均保存草稿, 并提示用户 "另存为".
*/
private fun writeTextWithCharsetTransactional(uri: Uri, text: String) {
val resolver = context.contentResolver
val (targetCharset, needBom) = when {
mHadBom -> {
mCurrentCharset to true
}
mCurrentCharsetConfidence >= MIN_CONFIDENCE_TO_WRITE_FILE -> {
mCurrentCharset to false
}
else -> {
DEFAULT_CHARSET_TO_WRITE_FILE to false
}
}
val newBytes = buildBytesToWrite(text, targetCharset, needBom)
// SAVE_PRE snapshot (best-effort).
// zh-CN: SAVE_PRE 快照 (尽力而为).
val oldBytes = runCatching {
resolver.openInputStream(uri)?.use { it.readBytes() }
}.getOrNull()
// Track history only when within size guardrails.
// zh-CN: 仅在满足大小护栏时纳管历史.
if (shouldTrackHistory(oldBytes, newBytes)) {
// TODO Hook HistoryRepository here.
// zh-CN: 在这里接入 HistoryRepository.
//
// Example:
// historyRepo.recordPreSave(uri = uri, oldBytes = oldBytes, meta = ...)
}
val newHash = sha256(newBytes)
try {
resolver.openOutputStream(uri, "rwt")?.use { out ->
out.write(newBytes)
out.flush()
} ?: throw IOException("Cannot open output stream for $uri")
val readBack = runCatching {
resolver.openInputStream(uri)?.use { it.readBytes() } ?: ByteArray(0)
}.getOrElse { e ->
throw IOException("Read-back failed after write: $uri", e)
}
val readBackHash = sha256(readBack)
if (!readBackHash.contentEquals(newHash)) {
throw IOException("Write verification failed (hash mismatch) for uri: $uri")
}
} catch (e: SecurityException) {
// Permission denied by provider or missing URI grants.
// zh-CN: provider 拒绝访问或缺少 URI 授权导致权限被拒绝.
handleSaveFailure(uri, oldBytes, newBytes, IOException("Permission denied for uri: $uri", e))
} catch (e: Throwable) {
handleSaveFailure(uri, oldBytes, newBytes, e)
}
}
private fun writeTextWithCharset(uri: Uri, text: String) {
context.contentResolver.openOutputStream(uri, "rwt")?.use { out ->
val (targetCharset, needBom) = when {
mHadBom -> {
mCurrentCharset to true
}
mCurrentCharsetConfidence >= MIN_CONFIDENCE_TO_WRITE_FILE -> {
mCurrentCharset to false
}
else -> {
DEFAULT_CHARSET_TO_WRITE_FILE to false
/**
* Handle transactional save failure.
*
* Steps:
* - Try rollback (best-effort).
* - Save emergency draft (always).
* - Prompt user actions on main thread.
*
* zh-CN:
*
* 处理事务保存失败.
*
* 步骤:
* - 尝试回滚 (尽力而为).
* - 保存草稿 (始终执行).
* - 在主线程弹窗提示用户后续操作.
*/
private fun handleSaveFailure(uri: Uri, oldBytes: ByteArray?, newBytes: ByteArray, error: Throwable): Nothing {
val resolver = context.contentResolver
// Try rollback to SAVE_PRE if possible.
// zh-CN: 若可能则回滚到 SAVE_PRE.
if (oldBytes != null) {
runCatching {
resolver.openOutputStream(uri, "rwt")?.use { out ->
out.write(oldBytes)
out.flush()
}
}
if (needBom) {
out.write(StringUtils.bomBytes(targetCharset))
}
val draftFile = runCatching {
EmergencyDraftStore(context).saveDraft(
displayName = name.ifBlank { "untitled" },
bytes = newBytes,
)
}.getOrNull()
// Prompt user on main thread.
// zh-CN: 在主线程提示用户.
DialogUtils.buildAndShowAdaptive(
MaterialDialog.Builder(context)
.title(R.string.error_save_failed)
.content(
buildString {
append(error.message ?: error.toString())
if (draftFile != null) {
append("\n\n")
append(context.getString(R.string.text_draft_file_path))
append(":\n")
append(draftFile.absolutePath)
}
}
)
.also { builder ->
if (draftFile != null) {
builder.neutralText(R.string.dialog_button_copy_path)
builder.neutralColorRes(R.color.dialog_button_hint)
builder.onNeutral { d, _ ->
ClipboardUtils.setClip(context, draftFile.absolutePath)
showSnack(d.view, R.string.text_already_copied_to_clip, false)
}
}
}
.negativeText(R.string.dialog_button_dismiss)
.negativeColorRes(R.color.dialog_button_default)
.onNegative { d, _ -> d.dismiss() }
.positiveText(R.string.dialog_button_save_as)
.positiveColorRes(R.color.dialog_button_attraction)
.onPositive { d, _ ->
// Guide user to save as a normal file path (file://).
// zh-CN: 引导用户另存为普通文件路径 (file://).
runCatching {
FileChooserDialogBuilder(context)
.title(R.string.text_save_to)
.dir(INTERNAL_STORAGE_ROOT)
.chooseDir()
.singleChoice()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ dir ->
// Determine extension from current uri path if possible.
// zh-CN: 尽可能从当前 uri.path 推导扩展名.
val ext = uri.path?.let { targetExtFromPath(it) } ?: ""
val dest = File(
dir.path,
buildSafeFileNameForSaveAs(
baseName = name,
ext = ext,
)
)
Schedulers.io().scheduleDirect {
runCatching {
dest.parentFile?.mkdirs()
File(dest.path).outputStream().use { it.write(newBytes) }
// If "Save as" succeeded, update current uri to the new file.
// zh-CN: 若另存为成功, 则将当前 uri 更新为新文件.
this@EditorView.uri = Uri.fromFile(dest)
this@EditorView.name = getNameWithoutExtension(dest.path)
// Mark as saved on UI thread.
// zh-CN: 在 UI 线程标记为已保存.
post {
d.dismiss()
editor.markTextAsSaved()
setMenuItemStatus(R.id.save, false)
}
}.onFailure {
post {
showSnack(d.view, R.string.error_save_failed, false)
showToast(context, it.message, true)
}
}
}
}, { e ->
e.printStackTrace()
})
}
}
.autoDismiss(false)
.cancelable(false)
)
if (error is IOException) {
throw error
}
throw IOException(error)
}
private fun buildBytesToWrite(text: String, charset: Charset, needBom: Boolean): ByteArray {
val body = text.toByteArray(charset)
if (!needBom) return body
val bom = StringUtils.bomBytes(charset)
return ByteArray(bom.size + body.size).also {
System.arraycopy(bom, 0, it, 0, bom.size)
System.arraycopy(body, 0, it, bom.size, body.size)
}
}
private fun shouldTrackHistory(oldBytes: ByteArray?, newBytes: ByteArray): Boolean {
val oldSize = oldBytes?.size ?: 0
val newSize = newBytes.size
return oldSize <= MAX_FILE_SIZE_TO_TRACK_BYTES && newSize <= MAX_FILE_SIZE_TO_TRACK_BYTES
}
private fun sha256(bytes: ByteArray): ByteArray {
return MessageDigest.getInstance("SHA-256").digest(bytes)
}
private fun targetExtFromPath(path: String): String {
val name = File(path).name
val dot = name.lastIndexOf('.')
if (dot <= 0 || dot >= name.length - 1) return ""
return name.substring(dot + 1)
}
private fun buildSafeFileNameForSaveAs(baseName: String, ext: String): String {
val raw = if (baseName.isBlank()) "untitled" else baseName
val sanitized = raw.replace(Regex("""[\\/:*?"<>|]"""), "_")
val niceExt = ext.trim().trimStart('.')
return if (niceExt.isBlank()) sanitized else "$sanitized.$niceExt"
}
// A minimal emergency draft store.
// zh-CN: 一个最小实现的草稿存储器.
private class EmergencyDraftStore(private val context: Context) {
private val draftsDir: File by lazy { File(context.filesDir, "drafts") }
fun saveDraft(displayName: String, bytes: ByteArray): File {
draftsDir.mkdirs()
val safeName = displayName
.ifBlank { "untitled" }
.replace(Regex("""[\\/:*?"<>|]"""), "_")
.lowercase(Locale.getDefault())
val now = System.currentTimeMillis()
val f = File(draftsDir, "${now}_${safeName}.bin")
f.outputStream().use { it.write(bytes) }
// Cleanup drafts after save (best-effort).
// zh-CN: 保存后顺便清理草稿 (尽力而为).
runCatching { cleanupLocked() }
return f
}
private fun cleanupLocked() {
val files = draftsDir.listFiles()?.toList() ?: return
if (files.isEmpty()) return
val now = System.currentTimeMillis()
// 1) Remove expired (older than 7 days).
// zh-CN: 1) 删除过期草稿 (超过 7 天).
val expiredBefore = now - DRAFT_MAX_DAYS_MS
files.forEach { f ->
if (f.lastModified() < expiredBefore) {
// noinspection ResultOfMethodCallIgnored
f.delete()
}
}
out.write(text.toByteArray(targetCharset))
} ?: throw IOException("Cannot open output stream for $uri")
// 2) Enforce total bytes limit (keep newest first).
// zh-CN: 2) 约束总容量上限 (优先保留最新).
val remained = draftsDir.listFiles()?.toList()?.sortedByDescending { it.lastModified() } ?: return
var total = remained.sumOf { it.length().coerceAtLeast(0L) }
if (total <= DRAFT_MAX_TOTAL_BYTES) return
for (f in remained.asReversed()) {
if (total <= DRAFT_MAX_TOTAL_BYTES) break
val len = f.length().coerceAtLeast(0L)
if (f.delete()) {
total -= len
}
}
}
}
fun forceStop() {
@@ -756,18 +1044,30 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
companion object {
private val TAG = EditorView::class.java.simpleName
private const val MIN_CONFIDENCE_TO_WRITE_FILE = 90
private val DEFAULT_CHARSET_TO_WRITE_FILE = StandardCharsets.UTF_8
// Max file size to track history snapshots.
// zh-CN: 纳管历史快照的最大文件大小.
private const val MAX_FILE_SIZE_TO_TRACK_BYTES: Int = 20 * 1024 * 1024
// Emergency draft cleanup window (7 days).
// zh-CN: 草稿清理时间窗口 (7 天).
private const val DRAFT_MAX_DAYS_MS: Long = 7L * 24L * 60L * 60L * 1000L
// Emergency draft total size limit (200MB).
// zh-CN: 草稿总容量上限 (200MB).
private const val DRAFT_MAX_TOTAL_BYTES: Long = 200L * 1024L * 1024L
// Internal storage root (no external SD).
// zh-CN: 内部存储根目录 (不访问外置 SD).
private const val INTERNAL_STORAGE_ROOT: String = "/storage/emulated/0"
const val EXTRA_PATH = "path"
const val EXTRA_NAME = "name"
const val EXTRA_CONTENT = "content"
const val EXTRA_READ_ONLY = "readOnly"
const val EXTRA_SAVE_ENABLED = "saveEnabled"
const val EXTRA_RUN_ENABLED = "runEnabled"
}
}

View File

@@ -1226,4 +1226,8 @@
<string name="text_write_system_settings">كتابة إعدادات النظام</string>
<string name="text_xiaomi_background_popup_permission">النوافذ المنبثقة في الخلفية</string>
<string name="error_refresh_failed">فشل التحديث</string>
<string name="error_save_failed">فشل الحفظ</string>
<string name="text_draft_file_path">مسار المسودة</string>
<string name="dialog_button_copy_path">نسخ</string>
<string name="dialog_button_save_as">حفظ باسم</string>
</resources>

View File

@@ -1221,4 +1221,8 @@
<string name="text_write_system_settings">Write system settings</string>
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
<string name="error_refresh_failed">Refresh failed</string>
<string name="error_save_failed">Save failed</string>
<string name="text_draft_file_path">Draft file path</string>
<string name="dialog_button_copy_path">Copy path</string>
<string name="dialog_button_save_as">Save as</string>
</resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Escribir la configuración del sistema</string>
<string name="text_xiaomi_background_popup_permission">Ventanas emergentes en segundo plano</string>
<string name="error_refresh_failed">Error al actualizar</string>
<string name="error_save_failed">Error al guardar</string>
<string name="text_draft_file_path">Ruta del borrador</string>
<string name="dialog_button_copy_path">Copiar</string>
<string name="dialog_button_save_as">Guardar como</string>
</resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Écrire les paramètres système</string>
<string name="text_xiaomi_background_popup_permission">Fenêtres contextuelles en arrière-plan</string>
<string name="error_refresh_failed">Echec de l\'actualisation</string>
<string name="error_save_failed">Echec de l\'enregistrement</string>
<string name="text_draft_file_path">Chemin du brouillon</string>
<string name="dialog_button_copy_path">Copier</string>
<string name="dialog_button_save_as">Enregistrer sous</string>
</resources>

View File

@@ -1225,4 +1225,8 @@
<string name="text_write_system_settings">システム設定の書き込み</string>
<string name="text_xiaomi_background_popup_permission">バックグラウンドでのポップアップ表示</string>
<string name="error_refresh_failed">更新に失敗しました</string>
<string name="error_save_failed">保存に失敗しました</string>
<string name="text_draft_file_path">下書きのパス</string>
<string name="dialog_button_copy_path">コピー</string>
<string name="dialog_button_save_as">名前を付けて保存</string>
</resources>

View File

@@ -1226,4 +1226,8 @@
<string name="text_write_system_settings">시스템 설정을 작성하십시오</string>
<string name="text_xiaomi_background_popup_permission">백그라운드 팝업</string>
<string name="error_refresh_failed">새로고침에 실패했습니다</string>
<string name="error_save_failed">저장에 실패했습니다</string>
<string name="text_draft_file_path">초안 경로</string>
<string name="dialog_button_copy_path">복사</string>
<string name="dialog_button_save_as">다른 이름으로 저장</string>
</resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Запись системных настроек</string>
<string name="text_xiaomi_background_popup_permission">Всплывающие окна в фоне</string>
<string name="error_refresh_failed">Не удалось обновить</string>
<string name="error_save_failed">Не удалось сохранить</string>
<string name="text_draft_file_path">Путь к черновику</string>
<string name="dialog_button_copy_path">Копировать</string>
<string name="dialog_button_save_as">Сохранить как</string>
</resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系統設置</string>
<string name="text_xiaomi_background_popup_permission">後台彈出界面</string>
<string name="error_refresh_failed">刷新失敗</string>
<string name="error_save_failed">保存失敗</string>
<string name="text_draft_file_path">草稿文件路徑</string>
<string name="dialog_button_copy_path">複製路徑</string>
<string name="dialog_button_save_as">另存為</string>
</resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系統設定</string>
<string name="text_xiaomi_background_popup_permission">後臺彈出介面</string>
<string name="error_refresh_failed">刷新失敗</string>
<string name="error_save_failed">儲存失敗</string>
<string name="text_draft_file_path">草稿檔案路徑</string>
<string name="dialog_button_copy_path">複製路徑</string>
<string name="dialog_button_save_as">另存為</string>
</resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系统设置</string>
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
<string name="error_refresh_failed">刷新失败</string>
<string name="error_save_failed">保存失败</string>
<string name="text_draft_file_path">草稿文件路径</string>
<string name="dialog_button_copy_path">复制路径</string>
<string name="dialog_button_save_as">另存为</string>
</resources>

View File

@@ -1482,4 +1482,8 @@
<string name="text_write_system_settings">Write system settings</string>
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
<string name="error_refresh_failed">Refresh failed</string>
<string name="error_save_failed">Save failed</string>
<string name="text_draft_file_path">Draft file path</string>
<string name="dialog_button_copy_path">Copy path</string>
<string name="dialog_button_save_as">Save as</string>
</resources>

View File

@@ -1,5 +1,5 @@
#Tue Feb 03 01:13:39 CST 2026
BUILD_TIME=1770052419058
#Tue Feb 03 15:47:53 CST 2026
BUILD_TIME=1770104873073
COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1
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
TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3688
VERSION_BUILD=3689
VERSION_NAME=6.7.0 Alpha19
VSCODE_EXT_REQUIRED_VERSION=1.0.13