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

View File

@@ -92,7 +92,7 @@ object DialogUtils {
@JvmOverloads @JvmOverloads
@Deprecated("Use showAdaptive instead.", ReplaceWith("showAdaptive(dialog, focusable)")) @Deprecated("Use showAdaptive instead.", ReplaceWith("showAdaptive(dialog, focusable)"))
@ReservedForCompatibility @ReservedForCompatibility
fun <T : MaterialDialog> showDialog(dialog: T, focusable: Boolean = true): T { fun showDialog(dialog: MaterialDialog, focusable: Boolean = true): MaterialDialog {
runOnMain { runOnMain {
// Prevent duplicated show. // Prevent duplicated show.
// zh-CN: 防止重复 show(). // zh-CN: 防止重复 show().
@@ -205,9 +205,9 @@ object DialogUtils {
* - 在后台线程 build 可能触发 "Can't create handler inside thread ..." 崩溃. * - 在后台线程 build 可能触发 "Can't create handler inside thread ..." 崩溃.
*/ */
@JvmStatic @JvmStatic
fun <T : MaterialDialog> buildAdaptive(builder: MaterialDialog.Builder): T { fun buildAdaptive(builder: MaterialDialog.Builder): MaterialDialog {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return buildAdaptive { builder.build() as T } return buildAdaptive { builder.build() }
} }
/** /**
@@ -216,12 +216,12 @@ object DialogUtils {
* zh-CN: 通过 callable 工厂在主线程 build 对话框. * zh-CN: 通过 callable 工厂在主线程 build 对话框.
*/ */
@JvmStatic @JvmStatic
fun <T : MaterialDialog> buildAdaptive(factory: Callable<T>): T { fun buildAdaptive(factory: Callable<MaterialDialog>): MaterialDialog {
if (Looper.getMainLooper() == Looper.myLooper()) { if (Looper.getMainLooper() == Looper.myLooper()) {
return factory.call() return factory.call()
} }
val ref = AtomicReference<T>() val ref = AtomicReference<MaterialDialog>()
val err = AtomicReference<Throwable?>() val err = AtomicReference<Throwable?>()
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
@@ -256,8 +256,8 @@ object DialogUtils {
*/ */
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun <T : MaterialDialog> buildAndShowAdaptive(builder: MaterialDialog.Builder, focusable: Boolean = true): T { fun buildAndShowAdaptive(builder: MaterialDialog.Builder, focusable: Boolean = true): MaterialDialog {
val dialog = buildAdaptive<T>(builder) val dialog = buildAdaptive(builder)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
return showDialog(dialog, focusable) return showDialog(dialog, focusable)
} }
@@ -269,7 +269,7 @@ object DialogUtils {
*/ */
@JvmStatic @JvmStatic
@JvmOverloads @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) val dialog = buildAdaptive(factory)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
return showDialog(dialog, focusable) return showDialog(dialog, focusable)
@@ -284,7 +284,7 @@ object DialogUtils {
} }
@JvmStatic @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 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.os.Parcelable
import android.text.TextUtils import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
@@ -27,13 +26,13 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.autojs.autojs.AutoJs 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.getEditorTextSize
import org.autojs.autojs.core.pref.Pref.setEditorTextSize import org.autojs.autojs.core.pref.Pref.setEditorTextSize
import org.autojs.autojs.engine.JavaScriptEngine import org.autojs.autojs.engine.JavaScriptEngine
import org.autojs.autojs.engine.ScriptEngine import org.autojs.autojs.engine.ScriptEngine
import org.autojs.autojs.event.BackPressedHandler.HostActivity import org.autojs.autojs.event.BackPressedHandler.HostActivity
import org.autojs.autojs.execution.ScriptExecution 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.AutoCompletion
import org.autojs.autojs.model.autocomplete.CodeCompletions import org.autojs.autojs.model.autocomplete.CodeCompletions
import org.autojs.autojs.model.autocomplete.Symbols 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.openByOtherApps
import org.autojs.autojs.model.script.Scripts.runWithBroadcastSender import org.autojs.autojs.model.script.Scripts.runWithBroadcastSender
import org.autojs.autojs.pio.PFiles.getNameWithoutExtension 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.pio.PFiles.write
import org.autojs.autojs.storage.file.TmpScriptFiles import org.autojs.autojs.storage.file.TmpScriptFiles
import org.autojs.autojs.tool.Callback 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.NormalToolbarFragment
import org.autojs.autojs.ui.edit.toolbar.SearchToolbarFragment import org.autojs.autojs.ui.edit.toolbar.SearchToolbarFragment
import org.autojs.autojs.ui.edit.toolbar.ToolbarFragment 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.log.LogActivity
import org.autojs.autojs.ui.widget.EWebView import org.autojs.autojs.ui.widget.EWebView
import org.autojs.autojs.ui.widget.SimpleTextWatcher 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.DisplayUtils.pxToSp
import org.autojs.autojs.util.DocsUtils.getUrl 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.Observers
import org.autojs.autojs.util.StringUtils import org.autojs.autojs.util.StringUtils
import org.autojs.autojs.util.ViewUtils.showSnack import org.autojs.autojs.util.ViewUtils.showSnack
@@ -80,11 +81,14 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.Locale
/** /**
* Created by Stardust on Sep 28, 2017. * Created by Stardust on Sep 28, 2017.
* Modified by SuperMonster003 as of May 1, 2023.
* Transformed by SuperMonster003 on 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") @SuppressLint("CheckResult")
class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener { class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener {
@@ -429,47 +433,331 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private fun redo() = editor.redo() private fun redo() = editor.redo()
fun save(): Observable<String> { fun save(): Observable<String> =
val path = uri.path!! Observable
val backPath = "$path.save"
move(path, backPath)
return Observable
.fromCallable { .fromCallable {
// Use a transactional save flow for both file:// and content://.
// zh-CN: 对 file:// 与 content:// 统一使用事务式保存流程.
editor.text.apply { editor.text.apply {
writeTextWithCharset(uri, this) writeTextWithCharsetTransactional(uri, this)
} }
} }
.observeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { .doOnNext {
editor.markTextAsSaved() editor.markTextAsSaved()
setMenuItemStatus(R.id.save, false) 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 -> * Handle transactional save failure.
val (targetCharset, needBom) = when { *
mHadBom -> { * Steps:
mCurrentCharset to true * - Try rollback (best-effort).
} * - Save emergency draft (always).
mCurrentCharsetConfidence >= MIN_CONFIDENCE_TO_WRITE_FILE -> { * - Prompt user actions on main thread.
mCurrentCharset to false *
} * zh-CN:
else -> { *
DEFAULT_CHARSET_TO_WRITE_FILE to false * 处理事务保存失败.
*
* 步骤:
* - 尝试回滚 (尽力而为).
* - 保存草稿 (始终执行).
* - 在主线程弹窗提示用户后续操作.
*/
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() { fun forceStop() {
@@ -756,18 +1044,30 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
companion object { companion object {
private val TAG = EditorView::class.java.simpleName
private const val MIN_CONFIDENCE_TO_WRITE_FILE = 90 private const val MIN_CONFIDENCE_TO_WRITE_FILE = 90
private val DEFAULT_CHARSET_TO_WRITE_FILE = StandardCharsets.UTF_8 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_PATH = "path"
const val EXTRA_NAME = "name" const val EXTRA_NAME = "name"
const val EXTRA_CONTENT = "content" const val EXTRA_CONTENT = "content"
const val EXTRA_READ_ONLY = "readOnly" const val EXTRA_READ_ONLY = "readOnly"
const val EXTRA_SAVE_ENABLED = "saveEnabled" const val EXTRA_SAVE_ENABLED = "saveEnabled"
const val EXTRA_RUN_ENABLED = "runEnabled" const val EXTRA_RUN_ENABLED = "runEnabled"
} }
} }

View File

@@ -1226,4 +1226,8 @@
<string name="text_write_system_settings">كتابة إعدادات النظام</string> <string name="text_write_system_settings">كتابة إعدادات النظام</string>
<string name="text_xiaomi_background_popup_permission">النوافذ المنبثقة في الخلفية</string> <string name="text_xiaomi_background_popup_permission">النوافذ المنبثقة في الخلفية</string>
<string name="error_refresh_failed">فشل التحديث</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> </resources>

View File

@@ -1221,4 +1221,8 @@
<string name="text_write_system_settings">Write system settings</string> <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="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_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> </resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Escribir la configuración del sistema</string> <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="text_xiaomi_background_popup_permission">Ventanas emergentes en segundo plano</string>
<string name="error_refresh_failed">Error al actualizar</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> </resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Écrire les paramètres système</string> <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="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_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> </resources>

View File

@@ -1225,4 +1225,8 @@
<string name="text_write_system_settings">システム設定の書き込み</string> <string name="text_write_system_settings">システム設定の書き込み</string>
<string name="text_xiaomi_background_popup_permission">バックグラウンドでのポップアップ表示</string> <string name="text_xiaomi_background_popup_permission">バックグラウンドでのポップアップ表示</string>
<string name="error_refresh_failed">更新に失敗しました</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> </resources>

View File

@@ -1226,4 +1226,8 @@
<string name="text_write_system_settings">시스템 설정을 작성하십시오</string> <string name="text_write_system_settings">시스템 설정을 작성하십시오</string>
<string name="text_xiaomi_background_popup_permission">백그라운드 팝업</string> <string name="text_xiaomi_background_popup_permission">백그라운드 팝업</string>
<string name="error_refresh_failed">새로고침에 실패했습니다</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> </resources>

View File

@@ -1224,4 +1224,8 @@
<string name="text_write_system_settings">Запись системных настроек</string> <string name="text_write_system_settings">Запись системных настроек</string>
<string name="text_xiaomi_background_popup_permission">Всплывающие окна в фоне</string> <string name="text_xiaomi_background_popup_permission">Всплывающие окна в фоне</string>
<string name="error_refresh_failed">Не удалось обновить</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> </resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系統設置</string> <string name="text_write_system_settings">修改系統設置</string>
<string name="text_xiaomi_background_popup_permission">後台彈出界面</string> <string name="text_xiaomi_background_popup_permission">後台彈出界面</string>
<string name="error_refresh_failed">刷新失敗</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> </resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系統設定</string> <string name="text_write_system_settings">修改系統設定</string>
<string name="text_xiaomi_background_popup_permission">後臺彈出介面</string> <string name="text_xiaomi_background_popup_permission">後臺彈出介面</string>
<string name="error_refresh_failed">刷新失敗</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> </resources>

View File

@@ -1222,4 +1222,8 @@
<string name="text_write_system_settings">修改系统设置</string> <string name="text_write_system_settings">修改系统设置</string>
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string> <string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
<string name="error_refresh_failed">刷新失败</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> </resources>

View File

@@ -1482,4 +1482,8 @@
<string name="text_write_system_settings">Write system settings</string> <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="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_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> </resources>

View File

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