From 6d964b7949c15a7a00134ebc6ef228f08044f330 Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Tue, 3 Feb 2026 15:48:23 +0800 Subject: [PATCH] =?UTF-8?q?6.7.0=20-=20Alpha19=20-=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=20M1=20-=20=E4=BB=A3=E7=A0=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E4=BF=9D=E5=AD=98=E6=96=87=E4=BB=B6=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=97=B6=E8=87=AA=E5=8A=A8=E5=AD=98=E4=B8=BA=E8=8D=89?= =?UTF-8?q?=E7=A8=BF=E5=B9=B6=E6=94=AF=E6=8C=81=E5=8F=A6=E5=AD=98=E4=B8=BA?= =?UTF-8?q?=E6=96=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/lang_zh-Hans.json | 1 + .../java/org/autojs/autojs/app/DialogUtils.kt | 18 +- .../org/autojs/autojs/ui/edit/EditorView.kt | 368 ++++++++++++++++-- app/src/main/res/values-ar/strings.xml | 4 + app/src/main/res/values-en/strings.xml | 4 + app/src/main/res/values-es/strings.xml | 4 + app/src/main/res/values-fr/strings.xml | 4 + app/src/main/res/values-ja/strings.xml | 4 + app/src/main/res/values-ko/strings.xml | 4 + app/src/main/res/values-ru/strings.xml | 4 + app/src/main/res/values-zh-rHK/strings.xml | 4 + app/src/main/res/values-zh-rTW/strings.xml | 4 + app/src/main/res/values-zh/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + version.properties | 6 +- 15 files changed, 391 insertions(+), 46 deletions(-) diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index f836c643..072f4ffc 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -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)_", "脚本项目配置文件保存时增加键名冲突检测机制防止键名歧义", diff --git a/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt b/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt index 4cc2b9f3..d5c36b3d 100644 --- a/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt +++ b/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt @@ -92,7 +92,7 @@ object DialogUtils { @JvmOverloads @Deprecated("Use showAdaptive instead.", ReplaceWith("showAdaptive(dialog, focusable)")) @ReservedForCompatibility - fun 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 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 buildAdaptive(factory: Callable): T { + fun buildAdaptive(factory: Callable): MaterialDialog { if (Looper.getMainLooper() == Looper.myLooper()) { return factory.call() } - val ref = AtomicReference() + val ref = AtomicReference() val err = AtomicReference() val latch = CountDownLatch(1) @@ -256,8 +256,8 @@ object DialogUtils { */ @JvmStatic @JvmOverloads - fun buildAndShowAdaptive(builder: MaterialDialog.Builder, focusable: Boolean = true): T { - val dialog = buildAdaptive(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 buildAndShowAdaptive(factory: Callable, focusable: Boolean = true): T { + fun buildAndShowAdaptive(factory: Callable, focusable: Boolean = true): MaterialDialog { val dialog = buildAdaptive(factory) @Suppress("DEPRECATION") return showDialog(dialog, focusable) @@ -284,7 +284,7 @@ object DialogUtils { } @JvmStatic - fun fixCheckBoxGravity(dialog: T): T = dialog.also { + fun fixCheckBoxGravity(dialog: MaterialDialog): MaterialDialog = dialog.also { it.view.findViewById(com.afollestad.materialdialogs.R.id.md_promptCheckbox)?.gravity = Gravity.CENTER_VERTICAL } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt index 591e052d..863eab8a 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt @@ -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 { - val path = uri.path!! - val backPath = "$path.save" - move(path, backPath) - return Observable + fun save(): Observable = + 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" - } - } \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 5098fa00..cb08f87c 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1226,4 +1226,8 @@ كتابة إعدادات النظام النوافذ المنبثقة في الخلفية فشل التحديث + فشل الحفظ + مسار المسودة + نسخ + حفظ باسم \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 5521e8b5..9bb6afbe 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1221,4 +1221,8 @@ Write system settings Display pop-up windows while running in the background Refresh failed + Save failed + Draft file path + Copy path + Save as \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3a954ce8..169d26ae 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1224,4 +1224,8 @@ Escribir la configuración del sistema Ventanas emergentes en segundo plano Error al actualizar + Error al guardar + Ruta del borrador + Copiar + Guardar como \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6c74a9d3..ad9e7292 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1224,4 +1224,8 @@ Écrire les paramètres système Fenêtres contextuelles en arrière-plan Echec de l\'actualisation + Echec de l\'enregistrement + Chemin du brouillon + Copier + Enregistrer sous \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1bdc1e6a..e2f7f375 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1225,4 +1225,8 @@ システム設定の書き込み バックグラウンドでのポップアップ表示 更新に失敗しました + 保存に失敗しました + 下書きのパス + コピー + 名前を付けて保存 \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 62dd9bca..05674df7 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1226,4 +1226,8 @@ 시스템 설정을 작성하십시오 백그라운드 팝업 새로고침에 실패했습니다 + 저장에 실패했습니다 + 초안 경로 + 복사 + 다른 이름으로 저장 \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1a596b60..46cb5b88 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1224,4 +1224,8 @@ Запись системных настроек Всплывающие окна в фоне Не удалось обновить + Не удалось сохранить + Путь к черновику + Копировать + Сохранить как \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 53445d37..3a5c50fa 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1222,4 +1222,8 @@ 修改系統設置 後台彈出界面 刷新失敗 + 保存失敗 + 草稿文件路徑 + 複製路徑 + 另存為 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 340a3119..0d4c267b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1222,4 +1222,8 @@ 修改系統設定 後臺彈出介面 刷新失敗 + 儲存失敗 + 草稿檔案路徑 + 複製路徑 + 另存為 \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index cbe487fa..22d9d9b5 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1222,4 +1222,8 @@ 修改系统设置 后台弹出界面 刷新失败 + 保存失败 + 草稿文件路径 + 复制路径 + 另存为 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfe77450..12476e10 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1482,4 +1482,8 @@ Write system settings Display pop-up windows while running in the background Refresh failed + Save failed + Draft file path + Copy path + Save as \ No newline at end of file diff --git a/version.properties b/version.properties index 53e56984..99e1322d 100644 --- a/version.properties +++ b/version.properties @@ -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