From 8a8d102e3f9edfd5144e30906082dace75d07c6f Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Thu, 12 Feb 2026 17:45:02 +0800 Subject: [PATCH] =?UTF-8?q?6.7.0=20-=20Alpha19=20-=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E5=A2=9E=E5=8A=A0=20"=E5=A4=9A?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E9=94=AE=E7=9B=98"=20-=20"=E7=AC=A6=E5=8F=B7?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE"=20=E8=8F=9C=E5=8D=95=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/lang_zh-Hans.json | 3 +- app/src/main/AndroidManifest.xml | 4 + .../autojs/model/autocomplete/Symbols.java | 26 - .../org/autojs/autojs/ui/edit/EditorMenu.java | 19 +- .../ui/edit/keyboard/SymbolsConfigStore.kt | 290 ++++ .../edit/keyboard/SymbolsSettingsActivity.kt | 1506 +++++++++++++++++ .../res/layout/activity_symbols_settings.xml | 145 ++ .../res/layout/fragment_symbols_toolbar.xml | 32 + .../main/res/layout/item_symbol_config.xml | 93 + .../menu_symbols_config_settings_more.xml | 13 + .../main/res/menu/menu_symbols_options.xml | 17 + app/src/main/res/values-ar/strings.xml | 39 + app/src/main/res/values-en/strings.xml | 39 + app/src/main/res/values-es/strings.xml | 39 + app/src/main/res/values-fr/strings.xml | 39 + app/src/main/res/values-ja/strings.xml | 39 + app/src/main/res/values-ko/strings.xml | 39 + app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values-ru/strings.xml | 39 + app/src/main/res/values-zh-rHK/strings.xml | 39 + app/src/main/res/values-zh-rTW/strings.xml | 39 + app/src/main/res/values-zh/strings.xml | 42 +- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 41 +- version.properties | 6 +- 25 files changed, 2547 insertions(+), 43 deletions(-) delete mode 100644 app/src/main/java/org/autojs/autojs/model/autocomplete/Symbols.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsConfigStore.kt create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsSettingsActivity.kt create mode 100644 app/src/main/res/layout/activity_symbols_settings.xml create mode 100644 app/src/main/res/layout/fragment_symbols_toolbar.xml create mode 100644 app/src/main/res/layout/item_symbol_config.xml create mode 100644 app/src/main/res/menu/menu_symbols_config_settings_more.xml create mode 100644 app/src/main/res/menu/menu_symbols_options.xml diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index d5ae0512..396e4b0c 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -1,7 +1,7 @@ { "$data": { "v6.7.0": { - "released_date": "2026/02/08", + "released_date": "2026/02/12", "feature": [ "插件中心功能, 支持插件的安装/卸载/更新等操作 (入口: 主页抽屉按钮/主页标签页)", "版本历史功能, 支持查看/恢复可编辑文件的历史版本 (入口: 主页抽屉按钮/文件管理器菜单/代码编辑器菜单)", @@ -39,6 +39,7 @@ "选择器的正则表达式参数支持使用标志 (i, m, s, u)", "正则表达式支持后瞻断言语法 _[`issue #464`](http://issues.autojs6.com/464)_", "文件管理器增加 \"移动到\" 及 \"复制到\" 菜单项, 支持操作中止及进度状态显示", + "代码编辑器增加 \"多功能键盘\" - \"符号设置\" 菜单项, 支持符号编辑及按配置级别进行符号的创建/导入/导出等", "主页抽屉增加 \"指针位置\" 工具", "主页抽屉增加 \"所有文件管理权限\" 开关", "主页抽屉增加 \"后台弹出界面\" 开关 (针对 [小米/Vivo] 设备)", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dcff625b..d3bb9269 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -310,6 +310,10 @@ android:taskAffinity="org.autojs.autojs.edit" android:theme="@style/EditorTheme" /> + + diff --git a/app/src/main/java/org/autojs/autojs/model/autocomplete/Symbols.java b/app/src/main/java/org/autojs/autojs/model/autocomplete/Symbols.java deleted file mode 100644 index f616a818..00000000 --- a/app/src/main/java/org/autojs/autojs/model/autocomplete/Symbols.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.autojs.autojs.model.autocomplete; - -import java.util.Arrays; - -/** - * Created by Stardust on Sep 28, 2017. - * Modified by SuperMonster003 as of Aug 24, 2022. - */ -public class Symbols { - - private static final CodeCompletions sSymbols = CodeCompletions.just(Arrays.asList( - ",", ".", "=", ";", "\"", "'", "/", "-", "_", - "(", ")", "[", "]", "{", "}", "<", ">", - "+", "*", "?", ":", "$", "#", "@", "`", - "\\", "&", "|", "!", "%", "×", "÷", - "∈", "∩", "∪", "∉", "⊙", "∅", "¥", "€", - "°", "℃", "∵", "∴", "±", "≠", "≈", - "α", "β", "γ", "λ", "μ", "π", "σ", "ω", - "®", "©", "♂", "♀", "√", "×", "✔", "✘", - "♥", "♠", "♦", "♣", "★", "◀", "▶", "●", "■", "▲", "◆" - )); - - public static CodeCompletions getSymbols() { - return sSymbols; - } -} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java index b3377cc0..f8541f64 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java @@ -2,6 +2,7 @@ package org.autojs.autojs.ui.edit; import android.annotation.SuppressLint; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.text.InputType; import android.text.TextUtils; @@ -10,7 +11,7 @@ import android.view.MenuItem; import androidx.annotation.Nullable; import com.afollestad.materialdialogs.MaterialDialog; import io.reactivex.android.schedulers.AndroidSchedulers; -import org.autojs.autojs.util.DialogUtils; +import kotlin.Unit; import org.autojs.autojs.core.pref.Language; import org.autojs.autojs.core.pref.Pref; import org.autojs.autojs.model.indices.AndroidClass; @@ -18,10 +19,12 @@ import org.autojs.autojs.model.indices.ClassSearchingItem; import org.autojs.autojs.script.JavaScriptFileSource; import org.autojs.autojs.ui.common.NotAskAgainDialog; import org.autojs.autojs.ui.edit.editor.CodeEditor; +import org.autojs.autojs.ui.edit.keyboard.SymbolsSettingsActivity; import org.autojs.autojs.ui.main.scripts.EditableFileInfoDialogManager; import org.autojs.autojs.ui.project.BuildActivity; import org.autojs.autojs.util.ClipboardUtils; import org.autojs.autojs.util.ConsoleUtils; +import org.autojs.autojs.util.DialogUtils; import org.autojs.autojs.util.IntentUtils; import org.autojs.autojs.util.IntentUtils.ToastExceptionHolder; import org.autojs.autojs.util.ViewUtils; @@ -36,6 +39,7 @@ import static org.autojs.autojs.util.StringUtils.key; /** * Created by Stardust on Sep 28, 2017. + * Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026. */ @SuppressWarnings({"ResultOfMethodCallIgnored", "unused"}) @SuppressLint("CheckResult") @@ -328,13 +332,11 @@ public class EditorMenu { } private void startSymbolsSettingsActivity() { - // TODO by SuperMonster003 on Oct 16, 2022. - - // new SymbolsSettingsActivity.IntentBuilder(mContext) - // .extra(mEditorView.getUri().getPath()) - // .start(); - - ViewUtils.showToast(mContext, R.string.text_under_development_content); + Intent intent = new Intent(mContext, SymbolsSettingsActivity.class); + IntentUtils.startSafely(intent, mContext, true, (t -> { + ViewUtils.showToast(mContext, t.getMessage(), true); + return Unit.INSTANCE; + })); } private boolean onEditOptionsSelected(MenuItem item) { @@ -479,5 +481,4 @@ public class EditorMenu { } return false; } - } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsConfigStore.kt b/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsConfigStore.kt new file mode 100644 index 00000000..ae18d8e2 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsConfigStore.kt @@ -0,0 +1,290 @@ +package org.autojs.autojs.ui.edit.keyboard + +import android.content.Context +import android.content.SharedPreferences +import android.content.res.Configuration +import org.autojs.autojs.core.pref.Language +import org.autojs.autojs.util.LocaleUtils +import org.autojs.autojs6.R +import org.json.JSONArray +import org.json.JSONObject +import java.util.Locale +import java.util.concurrent.ConcurrentHashMap +import androidx.core.content.edit + +/** + * Symbol profiles store for editor symbol bar. + * zh-CN: 编辑器符号栏的多配置存储. + * + * Created by JetBrains AI Assistant (GPT-5.2) on Feb 8, 2026. + */ +object SymbolsConfigStore { + + // Cache for default-name aliases. + // zh-CN: "默认名称别名集合" 的缓存 (按语言环境 key 缓存). + private val sDefaultAliasesCache = ConcurrentHashMap>() + + private const val PREF_NAME = "editor_symbols_config" + private const val KEY_ACTIVE_PROFILE = "active_profile" + private const val KEY_PROFILES_JSON = "profiles_json" + + // Internal stable id for default profile. + // zh-CN: 默认配置的内部稳定标识 (不随语言变化). + const val PROFILE_DEFAULT_ID = "__default__" + + data class SymbolItem( + val text: String, + val enabled: Boolean = true, + ) + + private fun prefs(context: Context): SharedPreferences = + context.applicationContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + + fun getActiveProfileName(context: Context): String = + prefs(context).getString(KEY_ACTIVE_PROFILE, PROFILE_DEFAULT_ID) ?: PROFILE_DEFAULT_ID + + fun setActiveProfileName(context: Context, name: String) { + prefs(context).edit { putString(KEY_ACTIVE_PROFILE, name) } + } + + fun getPrefillProfileName(context: Context): String { + val prefix = "config-" + val candidates = listProfiles(context) + .filter { it.startsWith(prefix) } + .mapNotNull { it.removePrefix(prefix).toIntOrNull() } + var candidate = 1 + while (true) { + if (!candidates.contains(candidate)) { + return "$prefix$candidate" + } + candidate++ + } + } + + fun listProfiles(context: Context): List { + val root = readRootOrCreate(context) + val profiles = root.optJSONObject("profiles") ?: JSONObject() + return profiles.keys().asSequence().toList().sorted() + } + + fun loadProfile(context: Context, name: String): List { + val root = readRootOrCreate(context) + val profiles = root.optJSONObject("profiles") ?: JSONObject() + val arr = profiles.optJSONArray(name) ?: return emptyList() + return (0 until arr.length()).mapNotNull { i -> + val o = arr.optJSONObject(i) ?: return@mapNotNull null + val t = o.optString("t", "") + if (!isValidSymbolText(t)) return@mapNotNull null + SymbolItem( + text = t, + enabled = o.optBoolean("e", true), + ) + } + } + + fun saveProfile(context: Context, name: String, items: List) { + val root = readRootOrCreate(context) + val profiles = root.optJSONObject("profiles") ?: JSONObject().also { root.put("profiles", it) } + + val arr = JSONArray() + items.forEach { item -> + if (!isValidSymbolText(item.text)) return@forEach + arr.put(JSONObject().apply { + put("t", item.text) + put("e", item.enabled) + }) + } + profiles.put(name, arr) + + persistRoot(context, root) + } + + fun deleteProfile(context: Context, name: String) { + val root = readRootOrCreate(context) + val profiles = root.optJSONObject("profiles") ?: return + profiles.remove(name) + persistRoot(context, root) + + // If active profile deleted, fallback to default. + // zh-CN: 删除当前激活配置时回退到默认. + if (getActiveProfileName(context) == name) { + setActiveProfileName(context, PROFILE_DEFAULT_ID) + } + } + + fun getEnabledSymbolsForActiveProfile(context: Context): List { + ensureDefaultProfileExists(context) + + val active = getActiveProfileName(context) + val items = loadProfile(context, active) + val enabled = items.filter { it.enabled }.map { it.text } + + return enabled + } + + fun ensureDefaultProfileExists(context: Context) { + val root = readRootOrCreate(context) + val profiles = root.optJSONObject("profiles") ?: JSONObject().also { root.put("profiles", it) } + if (profiles.has(PROFILE_DEFAULT_ID)) return + + val arr = JSONArray() + defaultSymbols().forEach { item -> + arr.put(JSONObject().apply { + put("t", item.text) + put("e", item.enabled) + }) + } + profiles.put(PROFILE_DEFAULT_ID, arr) + + persistRoot(context, root) + + // Also set SharedPreferences active_profile if absent. + // zh-CN: 也写入 active_profile, 便于快速读取. + if (!prefs(context).contains(KEY_ACTIVE_PROFILE)) { + setActiveProfileName(context, PROFILE_DEFAULT_ID) + } + } + + /** + * Export a profile to JSON object. + * + * Format: + * { + * "name": "xxx", + * "items": [ {"t":"...", "e":true}, ... ] + * } + */ + fun exportProfileToJson(context: Context, name: String): JSONObject { + val items = loadProfile(context, name) + val arr = JSONArray() + items.forEach { item -> + if (!isValidSymbolText(item.text)) return@forEach + arr.put(JSONObject().apply { + put("t", item.text) + put("e", item.enabled) + }) + } + return JSONObject().apply { + put("name", name) + put("items", arr) + } + } + + /** + * Import profile from JSON object. + * Returns imported profile name and items. + * + * Rules: + * - Symbol text must be non-blank and contain NO whitespace chars. + * - Duplicates are removed (keep first occurrence order). + */ + fun importProfileFromJson(json: JSONObject): Pair> { + val name = json.optString("name", "").trim() + require(name.isNotBlank()) { "Invalid profile name" } + + val arr = json.optJSONArray("items") ?: JSONArray() + + val out = ArrayList(arr.length()) + val seen = HashSet() + + for (i in 0 until arr.length()) { + val o = arr.optJSONObject(i) ?: continue + val t = o.optString("t", "") + if (!isValidSymbolText(t)) continue + if (!seen.add(t)) continue + + out.add(SymbolItem(text = t, enabled = o.optBoolean("e", true))) + } + + return name to out + } + + /** + * A symbol is valid iff: + * - not blank + * - contains no whitespace chars (\\s) + */ + fun isValidSymbolText(text: String?): Boolean { + val s = text ?: return false + if (s.isBlank()) return false + return !s.any { it.isWhitespace() } + } + + /** + * Display name of default profile under current app language. + * zh-CN: 当前 App 语言下 "默认配置" 的显示名 (来自 strings.xml). + */ + fun getDefaultProfileDisplayName(context: Context): String { + return context.getString(R.string.text_symbols_profile_default_name) + } + + /** + * All localized aliases for "default" across all supported app languages. + * zh-CN: 遍历 Language 枚举, 收集所有语言下 "默认" 的显示名, 作为保留字集合. + */ + fun getAllDefaultNameAliases(context: Context): Set { + val appLang = Language.getPrefLanguageOrNull() + val appKey = appLang?.languageTag ?: "null" + val sysKey = LocaleUtils.getSystemLocale().toLanguageTag() + val cacheKey = "app=$appKey|sys=$sysKey" + + sDefaultAliasesCache[cacheKey]?.let { return it } + + val computed = LinkedHashSet().apply { + add(PROFILE_DEFAULT_ID) + + Language.values().forEach { lang -> + if (lang == Language.AUTO) return@forEach + val s = getStringForLocale(context, lang.locale, R.string.text_symbols_profile_default_name) + .trim() + if (s.isNotBlank()) add(s) + } + + add(getDefaultProfileDisplayName(context).trim()) + }.toSet() + + sDefaultAliasesCache[cacheKey] = computed + return computed + } + + fun isReservedDefaultName(context: Context, name: String?): Boolean { + val n = name?.trim().orEmpty() + if (n.isBlank()) return false + return getAllDefaultNameAliases(context).any { it == n } + } + + private fun getStringForLocale(context: Context, locale: Locale, resId: Int): String { + val cfg = Configuration(context.resources.configuration) + cfg.setLocale(locale) + val localized = context.createConfigurationContext(cfg) + return localized.resources.getString(resId) + } + + private fun readRootOrCreate(context: Context): JSONObject { + val sp = prefs(context) + val raw = sp.getString(KEY_PROFILES_JSON, null) + return try { + if (raw.isNullOrBlank()) JSONObject() else JSONObject(raw) + } catch (_: Throwable) { + JSONObject() + }.also { r -> + if (!r.has("profiles")) r.put("profiles", JSONObject()) + } + } + + private fun persistRoot(context: Context, root: JSONObject) { + prefs(context).edit { putString(KEY_PROFILES_JSON, root.toString()) } + } + + private fun defaultSymbols(): List = listOf( + ",", ".", "=", ";", "\"", "'", "/", "-", "_", + "(", ")", "[", "]", "{", "}", "<", ">", + "+", "*", "?", ":", "$", "#", "@", "`", + "\\", "&", "|", "!", "%", "×", "÷", + "∈", "∩", "∪", "∉", "⊙", "∅", "¥", "€", + "°", "℃", "∵", "∴", "±", "≠", "≈", + "α", "β", "γ", "λ", "μ", "π", "σ", "ω", + "®", "©", "♂", "♀", "√", "×", "✔", "✘", + "♥", "♠", "♦", "♣", "★", "◀", "▶", "●", "■", "▲", "◆", + ).map { SymbolItem(it, true) } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsSettingsActivity.kt b/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsSettingsActivity.kt new file mode 100644 index 00000000..426683ca --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/keyboard/SymbolsSettingsActivity.kt @@ -0,0 +1,1506 @@ +package org.autojs.autojs.ui.edit.keyboard + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.text.InputType +import android.view.Gravity +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.CheckBox +import android.widget.EditText +import android.widget.ImageView +import android.widget.PopupMenu +import android.widget.Spinner +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.afollestad.materialdialogs.MaterialDialog +import org.autojs.autojs.ui.BaseActivity +import org.autojs.autojs.util.DialogUtils +import org.autojs.autojs.util.DialogUtils.choiceWidgetThemeColor +import org.autojs.autojs.util.DialogUtils.widgetThemeColor +import org.autojs.autojs.util.ViewUtils +import org.autojs.autojs.util.ViewUtils.excludePaddingClippableViewFromBottomNavigationBar +import org.autojs.autojs.util.ViewUtils.setMenuIconsColorByThemeColorLuminance +import org.autojs.autojs6.R +import org.autojs.autojs6.databinding.ActivitySymbolsSettingsBinding +import org.autojs.autojs6.databinding.FragmentSymbolsToolbarBinding +import org.json.JSONObject +import java.nio.charset.StandardCharsets + +/** + * Modified by SuperMonster003 as of Feb 11, 2026. + * Created by JetBrains AI Assistant (GPT-5.2) on Feb 11, 2026. + * Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 12, 2026. + * Modified by SuperMonster003 as of Feb 12, 2026. + */ +@SuppressLint("NotifyDataSetChanged") +class SymbolsSettingsActivity : BaseActivity() { + + private lateinit var menu: Menu + + private lateinit var activityBinding: ActivitySymbolsSettingsBinding + private lateinit var toolbarBinding: FragmentSymbolsToolbarBinding + + private lateinit var profileSpinner: Spinner + private lateinit var recycler: RecyclerView + + private lateinit var ivNewProfile: ImageView + private lateinit var ivDeleteProfile: ImageView + private lateinit var ivMoreProfile: ImageView + + private lateinit var emptyHintContainer: View + + private var currentProfileName: String = "" + + private val items = mutableListOf() + private lateinit var adapter: SymbolsAdapter + + private var itemTouchHelper: ItemTouchHelper? = null + + // Baseline items for current profile (i.e. last saved state / reset target). + // zh-CN: 当前配置的基线 items (即最近一次保存状态/重置目标). + private var baselineItems: List = emptyList() + + // Undo/redo snapshot stacks (each snapshot is a full list). + // zh-CN: 撤销/重做快照栈 (每个快照是一份完整列表). + private val undoStack = ArrayDeque>() + private val redoStack = ArrayDeque>() + + // Save button sticky flag. + // zh-CN: 保存按钮粘性标记. + private var saveSticky: Boolean = false + + // RecyclerView user scrolling flag. + // zh-CN: RecyclerView 用户滚动标记. + @Volatile + private var recyclerUserScrolling: Boolean = false + + // Current editing session (single editor at a time). + // zh-CN: 当前编辑会话 (同一时刻仅允许一个编辑器). + private var editing: EditingSession? = null + + // Guard flag to avoid handling spinner callback when we programmatically change selection. + // zh-CN: 防护标记, 避免代码设置 spinner 选中项时触发回调逻辑. + private var suppressSpinnerCallback: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val activityBinding = ActivitySymbolsSettingsBinding.inflate(layoutInflater).also { + setContentView(it.root) + this.activityBinding = it + } + + setToolbarAsBack(R.string.text_symbols_settings) + + toolbarBinding = FragmentSymbolsToolbarBinding.inflate(layoutInflater, activityBinding.toolbarMenu, true).also { + it.actionUndo.apply { + setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + performUndo() + } + } + it.actionRedo.apply { + setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + performRedo() + } + } + it.actionSave.apply { + setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + saveWithDefaultProtectionOrCreateNew() + } + setOnLongClickListener { + promptCreateProfileByName( + titleRes = R.string.text_save_configuration_as, + contentRes = R.string.text_set_a_name_for_the_new_configuration, + baseItems = items.toList(), + afterCreated = null, + ) + true + } + } + } + + // Initialize action buttons to disabled state. + // zh-CN: 初始化三个动作按钮为不可用状态. + updateActionButtons() + + SymbolsConfigStore.ensureDefaultProfileExists(this) + + profileSpinner = activityBinding.profileSpinner + + recycler = activityBinding.recycler.apply { + excludePaddingClippableViewFromBottomNavigationBar() + } + + ivNewProfile = activityBinding.ivNewProfile + ivDeleteProfile = activityBinding.ivDeleteProfile + ivMoreProfile = activityBinding.ivMoreProfile + + emptyHintContainer = activityBinding.symbolsEmptyHintContainer.apply { + setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ItemAction) + + val newText = findFirstUniqueUnicodeSymbol() + + recordChange(markSaveSticky = true) { + items.add(0, SymbolsConfigStore.SymbolItem(newText, true)) + } + + adapter.notifyItemInserted(0) + recycler.smoothScrollToPosition(0) + + updateToggleAllButton() + updateActionButtons() + + adapter.requestEditAt(0, isNewItem = true) + } + } + + createAdapter() + + recycler.layoutManager = LinearLayoutManager(this) + recycler.adapter = adapter + + recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + recyclerUserScrolling = newState != RecyclerView.SCROLL_STATE_IDLE + + // If scrolling ends and the editor already lost focus during scroll, + // commit it now. + // + // zh-CN: + // 若滚动结束且编辑器在滚动期间已丢失焦点, + // 则此时再提交一次. + if (!recyclerUserScrolling) { + commitEditingIfNeeded(reason = CommitReason.ScrollIdle) + } + } + }) + + attachDragToReorder() + + ivNewProfile.setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + runWithUnsavedChangesGuard(onProceed = { promptCreateProfileWithBasePicker() }) + } + + ivDeleteProfile.setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + runWithUnsavedChangesGuard(onProceed = { promptDeleteProfile() }) + } + + ivMoreProfile.setOnClickListener { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + showMorePopup(anchor = it) + } + + setupProfilesSpinner() + + // Apply empty hint status after initial profile load. + // zh-CN: 初次加载配置后应用空提示状态. + updateEmptyHint() + } + + private fun updateEmptyHint() { + emptyHintContainer.isVisible = items.isEmpty() + } + + override fun finish() { + commitEditingIfNeeded(reason = CommitReason.LeavingScreen) + runWithUnsavedChangesGuard(onProceed = { finishAndRemoveTask() }, isExit = true) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + this.menu = menu + menuInflater.inflate(R.menu.menu_symbols_options, menu) + updateToggleAllButton() + + activityBinding.toolbar.setMenuIconsColorByThemeColorLuminance(this) + + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + commitEditingIfNeeded(reason = CommitReason.ToolbarAction) + + when (item.itemId) { + R.id.action_toggle_all -> { + if (items.isEmpty()) return true + + recordChange(markSaveSticky = true) { + val allEnabled = items.all { it.enabled } + val target = !allEnabled + for (i in items.indices) { + items[i] = items[i].copy(enabled = target) + } + } + + adapter.notifyDataSetChanged() + updateToggleAllButton() + return true + } + R.id.action_import -> { + runWithUnsavedChangesGuard(onProceed = { startImportJson() }) + return true + } + R.id.action_export -> { + runWithUnsavedChangesGuard(onProceed = { startExportJson() }) + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun createAdapter() { + adapter = SymbolsAdapter( + data = items, + onToggle = { pos, enabled -> + commitEditingIfNeeded(reason = CommitReason.ItemAction) + + if (pos < 0 || pos >= items.size) return@SymbolsAdapter + recordChange(markSaveSticky = true) { + items[pos] = items[pos].copy(enabled = enabled) + } + updateToggleAllButton() + adapter.notifyItemChanged(pos) + updateActionButtons() + }, + onAdd = { pos -> + commitEditingIfNeeded(reason = CommitReason.ItemAction) + + if (pos < 0 || pos >= items.size) return@SymbolsAdapter + + val newText = findFirstUniqueUnicodeSymbol() + + recordChange(markSaveSticky = true) { + items.add(pos + 1, SymbolsConfigStore.SymbolItem(newText, true)) + } + + adapter.notifyItemInserted(pos + 1) + recycler.smoothScrollToPosition(pos + 1) + + updateToggleAllButton() + updateActionButtons() + + // Auto enter edit mode for the newly inserted item. + // zh-CN: 新增条目后自动进入编辑态. + adapter.requestEditAt(pos + 1, isNewItem = true) + }, + onDelete = { pos -> + commitEditingIfNeeded(reason = CommitReason.ItemAction) + + if (pos < 0 || pos >= items.size) return@SymbolsAdapter + + recordChange(markSaveSticky = true) { + items.removeAt(pos) + } + + adapter.notifyItemRemoved(pos) + updateToggleAllButton() + updateActionButtons() + }, + onBeginEdit = { session -> + // Ensure single editor. + // zh-CN: 确保同一时刻仅存在一个编辑器. + if (editing != null && editing?.position != session.position) { + commitEditingIfNeeded(reason = CommitReason.SwitchEditor) + } + + editing = session + + // Auto show keyboard and select all. + // zh-CN: 自动全选并弹出软键盘. + session.editText.post { + session.editText.requestFocus() + session.editText.selectAll() + showKeyboard(session.editText) + } + }, + + onEditFocusChanged = { session, hasFocus -> + if (hasFocus) return@SymbolsAdapter + + // Focus loss during scrolling should not be treated as leaving edit area. + // We will commit when scroll becomes idle. + // + // zh-CN: + // 滚动期间的焦点丢失不视为离开编辑区域, + // 等滚动停止后再提交. + if (recyclerUserScrolling) return@SymbolsAdapter + + commitEditingIfNeeded(reason = CommitReason.FocusLost) + }, + onRequestDrag = { vh -> + commitEditingIfNeeded(reason = CommitReason.ItemAction) + itemTouchHelper?.startDrag(vh) + } + ) + } + + private fun canEditStructureNow(): Boolean = + currentProfileName.isNotBlank() + + private fun applyUiForProfile() { + adapter.notifyDataSetChanged() + updateToggleAllButton() + + // Reset action buttons on profile switch. + // zh-CN: 切换配置后重置动作按钮状态. + updateActionButtons() + } + + private fun updateToggleAllButton() { + // Rule: + // - Only when ALL checked -> show "取消全选" and action is deselect all. + // - Otherwise -> show "全选" and action is select all. + // + // zh-CN: 规则: + // - 仅当全部勾选时显示 "取消全选", 功能为全部取消. + // - 其它情况一律显示 "全选", 功能为全部勾选. + if (::menu.isInitialized) { + val allEnabled = items.isNotEmpty() && items.all { it.enabled } + menu.findItem(R.id.action_toggle_all)?.let { menuItem -> + menuItem.title = getString(if (allEnabled) R.string.text_deselect_all else R.string.text_select_all) + } + } + } + + private fun switchToProfile(name: String) { + currentProfileName = name + SymbolsConfigStore.setActiveProfileName(this, name) + + val loaded = SymbolsConfigStore.loadProfile(this, name) + + items.clear() + items.addAll(loaded) + + baselineItems = loaded.toList() + + undoStack.clear() + redoStack.clear() + + // Reset sticky save status on profile switch. + // zh-CN: 切换配置时重置保存粘性状态. + saveSticky = false + + editing = null + + adapter.notifyDataSetChanged() + + applyUiForProfile() + updateToggleAllButton() + updateActionButtons() + updateEmptyHint() + } + + private fun rebuildProfilesSpinnerAndSelect(internalName: String) { + val internalProfiles = SymbolsConfigStore.listProfiles(this).ifEmpty { + listOf(SymbolsConfigStore.PROFILE_DEFAULT_ID) + } + + val entries = internalProfiles.map { internal -> + val display = if (internal == SymbolsConfigStore.PROFILE_DEFAULT_ID) { + SymbolsConfigStore.getDefaultProfileDisplayName(this) + } else internal + ProfileEntry(internalName = internal, displayName = display) + } + + val spinAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, entries.map { it.displayName }).also { + it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + + suppressSpinnerCallback = true + profileSpinner.adapter = spinAdapter + + val idx = entries.indexOfFirst { it.internalName == internalName }.coerceAtLeast(0) + profileSpinner.setSelection(idx, false) + suppressSpinnerCallback = false + + // Ensure the page actually switches to the selected profile. + // zh-CN: 确保页面确实切换到目标配置. + if (currentProfileName != internalName) { + switchToProfile(internalName) + } + } + + private fun setupProfilesSpinner() { + val internalProfiles = SymbolsConfigStore.listProfiles(this).ifEmpty { + listOf(SymbolsConfigStore.PROFILE_DEFAULT_ID) + } + + val entries = internalProfiles.map { internal -> + val display = if (internal == SymbolsConfigStore.PROFILE_DEFAULT_ID) { + SymbolsConfigStore.getDefaultProfileDisplayName(this) + } else internal + ProfileEntry(internalName = internal, displayName = display) + } + + val activeInternal = SymbolsConfigStore.getActiveProfileName(this) + + val spinAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, entries.map { it.displayName }).also { + it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + profileSpinner.adapter = spinAdapter + + val idx = entries.indexOfFirst { it.internalName == activeInternal }.coerceAtLeast(0) + profileSpinner.setSelection(idx, false) + + profileSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + if (suppressSpinnerCallback) return + + val chosen = entries.getOrNull(position) ?: return + if (chosen.internalName == currentProfileName) return + + commitEditingIfNeeded(reason = CommitReason.SwitchProfile) + + runWithUnsavedChangesGuard( + onProceed = { + switchToProfile(chosen.internalName) + }, + onCancel = { + val oldIdx = entries.indexOfFirst { it.internalName == currentProfileName }.coerceAtLeast(0) + suppressSpinnerCallback = true + profileSpinner.setSelection(oldIdx, false) + suppressSpinnerCallback = false + } + ) + } + + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + } + + switchToProfile(entries[idx].internalName) + } + + private fun isDirty(): Boolean = items != baselineItems + + private fun pushUndoSnapshot() { + undoStack.addLast(items.toList()) + redoStack.clear() + } + + private fun applySnapshot(snapshot: List) { + items.clear() + items.addAll(snapshot) + adapter.notifyDataSetChanged() + updateToggleAllButton() + updateActionButtons() + updateEmptyHint() + } + + private fun recordChange(markSaveSticky: Boolean, change: () -> Unit) { + pushUndoSnapshot() + change() + + if (markSaveSticky) { + // Once turned on, do not auto turn off except by explicit save or full undo-to-baseline. + // zh-CN: 一旦亮起, 除非显式保存或完全撤销回基线, 否则不自动熄灭. + saveSticky = true + } + + updateActionButtons() + updateEmptyHint() + } + + private fun performUndo() { + if (undoStack.isEmpty()) return + + // Undo will end any editing session. + // zh-CN: 撤销会结束当前编辑会话. + editing = null + + redoStack.addLast(items.toList()) + val prev = undoStack.removeLast() + + applySnapshot(prev) + + // Auto turn off sticky save only when fully undone and state equals baseline. + // zh-CN: 仅当撤销到尽头且状态等于基线时, 才允许自动熄灭保存按钮. + if (undoStack.isEmpty() && items == baselineItems) { + saveSticky = false + } + + updateActionButtons() + updateEmptyHint() + } + + private fun performRedo() { + if (redoStack.isEmpty()) return + + editing = null + + undoStack.addLast(items.toList()) + val next = redoStack.removeLast() + + applySnapshot(next) + + // Redo implies user is making changes again. + // zh-CN: 重做意味着用户再次推进修改. + saveSticky = true + + updateActionButtons() + updateEmptyHint() + } + + private fun updateActionButtons() { + val undoEnabled = undoStack.isNotEmpty() + val redoEnabled = redoStack.isNotEmpty() + + // Save button strategy: + // - Sticky: once enabled it stays enabled. + // - Can auto turn off only when undoStack is empty AND items == baselineItems. + // - Explicit save will turn it off. + // + // zh-CN: + // - 粘性: 一旦亮起保持亮起. + // - 仅在 undo 为空且 items == baseline 时可自动熄灭. + // - 用户手动点击保存后熄灭. + val saveEnabled = saveSticky || isDirty() + + toolbarBinding.actionUndo.isEnabled = undoEnabled + toolbarBinding.actionRedo.isEnabled = redoEnabled + toolbarBinding.actionSave.isEnabled = saveEnabled + } + + private fun saveWithDefaultProtectionOrCreateNew(afterSaved: (() -> Unit)? = null) { + if (currentProfileName.isBlank()) return + + // Explicit save: turn off sticky. + // zh-CN: 显式保存: 关闭粘性. + saveSticky = false + + if (!isDirty()) { + updateActionButtons() + afterSaved?.invoke() + return + } + + if (currentProfileName == SymbolsConfigStore.PROFILE_DEFAULT_ID) { + promptCreateProfileByName( + contentRes = R.string.text_default_profile_overwrite_hint, + baseItems = items.toList(), + afterCreated = afterSaved, + ) + return + } + + SymbolsConfigStore.saveProfile(this, currentProfileName, items.toList()) + baselineItems = items.toList() + + // IMPORTANT: Do NOT clear undo/redo after save. + // zh-CN: 重要: 保存后不要清空撤销/重做栈. + ViewUtils.showToast(this, R.string.text_done) + updateActionButtons() + + afterSaved?.invoke() + } + + private fun saveWithDefaultProtectionOrCreateNew() { + saveWithDefaultProtectionOrCreateNew(afterSaved = null) + } + + private fun runWithUnsavedChangesGuard( + onProceed: () -> Unit, + onCancel: (() -> Unit)? = null, + isExit: Boolean = false, + ) { + if (!(saveSticky || isDirty())) { + onProceed() + return + } + + val contentRes = when { + isExit -> R.string.warn_exit_without_saving_settings + else -> R.string.warn_continue_operation_without_saving_settings + } + + val negativeTextRes = when { + isExit -> R.string.text_exit_directly + else -> R.string.dialog_button_discard_changes + } + + val positiveTextRes = when { + isExit -> R.string.text_save_and_exit + else -> R.string.dialog_button_save_and_continue + } + + // Prompt to save or discard. "Discard" still proceeds. + // zh-CN: 提示保存或放弃. 选择 "放弃" 仍继续执行后续操作. + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_prompt) + .content(contentRes) + .neutralText(R.string.dialog_button_back) + .neutralColorRes(R.color.dialog_button_default) + .onNeutral { _, _ -> + onCancel?.invoke() + } + .negativeText(negativeTextRes) + .negativeColorRes(R.color.dialog_button_caution) + .onNegative { _, _ -> + onProceed() + } + .positiveText(positiveTextRes) + .positiveColorRes(R.color.dialog_button_warn) + .onPositive { _, _ -> + saveWithDefaultProtectionOrCreateNew(afterSaved = { onProceed() }) + } + .build() + } + } + + private fun showMorePopup(anchor: View) { + val popupMenu = PopupMenu(anchor.context, anchor, Gravity.END) + popupMenu.menuInflater.inflate(R.menu.menu_symbols_config_settings_more, popupMenu.menu) + popupMenu.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_reset -> { + performResetToBaseline() + true + } + R.id.action_manage -> { + runWithUnsavedChangesGuard(onProceed = { promptManageProfiles() }) + true + } + else -> false + } + } + popupMenu.show() + } + + private fun performResetToBaseline() { + commitEditingIfNeeded(reason = CommitReason.ItemAction) + + if (baselineItems == items) return + + // Reset is treated as a normal change, and should NOT turn off save sticky. + // zh-CN: 重置视为普通操作, 且不应熄灭保存粘性. + recordChange(markSaveSticky = true) { + items.clear() + items.addAll(baselineItems) + } + + adapter.notifyDataSetChanged() + updateToggleAllButton() + updateActionButtons() + } + + private fun promptManageProfiles() { + val profiles = SymbolsConfigStore.listProfiles(this) + .filter { it != SymbolsConfigStore.PROFILE_DEFAULT_ID } + .sorted() + + if (profiles.isEmpty()) { + ViewUtils.showToast(this, R.string.text_no_data, true) + return + } + + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_manage) + .items(profiles) + .itemsCallback { d, _, which, _ -> + d.dismiss() + val name = profiles.getOrNull(which) ?: return@itemsCallback + promptManageSingleProfile(name) + } + .build() + } + } + + private fun promptManageSingleProfile(name: String) { + val actions = listOf( + getString(R.string.text_open), + getString(R.string.text_rename), + getString(R.string.text_delete), + ) + + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(name) + .items(actions) + .itemsCallback { d, _, which, _ -> + d.dismiss() + when (which) { + 0 -> { + // Switch to selected profile immediately (no recreate). + // zh-CN: 立即切换到选中配置 (不依赖 recreate). + rebuildProfilesSpinnerAndSelect(name) + } + 1 -> promptRenameProfile(name) + 2 -> { + currentProfileName = name + promptDeleteProfile() + } + } + } + .build() + } + } + + private fun promptRenameProfile(oldName: String) { + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_rename) + .inputType(InputType.TYPE_CLASS_TEXT) + .input(getString(R.string.text_symbols_profile_name_hint), oldName) { d, input -> + val newName = input?.toString()?.trim().orEmpty() + if (newName.isBlank()) { + ViewUtils.showSnack(d.view, R.string.text_symbols_profile_name_invalid, true) + return@input + } + if (SymbolsConfigStore.isReservedDefaultName(this, newName)) { + ViewUtils.showSnack(d.view, R.string.text_symbols_profile_name_cannot_be_default, true) + return@input + } + val existing = SymbolsConfigStore.listProfiles(this).toSet() + if (existing.contains(newName)) { + ViewUtils.showSnack(d.view, R.string.text_symbols_profile_name_conflict, true) + return@input + } + + val oldItems = SymbolsConfigStore.loadProfile(this, oldName) + SymbolsConfigStore.saveProfile(this, newName, oldItems) + SymbolsConfigStore.deleteProfile(this, oldName) + SymbolsConfigStore.setActiveProfileName(this, newName) + + d.dismiss() + recreate() + } + .widgetThemeColor() + .positiveText(R.string.dialog_button_confirm) + .negativeText(R.string.dialog_button_cancel) + .autoDismiss(false) + .build() + } + } + + private fun promptCreateProfileWithBasePicker() { + val libraryProfiles = SymbolsConfigStore.listProfiles(this) + .filter { it != SymbolsConfigStore.PROFILE_DEFAULT_ID } + .sorted() + + val baseLabels = ArrayList().apply { + add(getString(R.string.text_empty_configuration)) + add(getString(R.string.text_default_configuration)) + add(getString(R.string.text_current_page_configuration)) + addAll(libraryProfiles) + } + + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_symbols_new_profile) + .content(getString(R.string.text_select_a_configuration_template)) + .items(baseLabels) + .itemsCallbackSingleChoice(0) { d, _, which, _ -> + d.dismiss() + + // IMPORTANT: if base is "empty", keep it empty. + // zh-CN: 重要: 若基于 "空白配置", 则必须保持空白. + val baseItems = when (which) { + 0 -> emptyList() + 1 -> SymbolsConfigStore.loadProfile(this, SymbolsConfigStore.PROFILE_DEFAULT_ID) + 2 -> items.toList() + else -> { + val idx = which - 3 + val name = libraryProfiles.getOrNull(idx) ?: return@itemsCallbackSingleChoice false + SymbolsConfigStore.loadProfile(this, name) + } + } + + promptCreateProfileByName( + contentRes = R.string.text_set_a_name_for_the_new_configuration, + baseItems = baseItems, + afterCreated = null, + ) + return@itemsCallbackSingleChoice true + } + .choiceWidgetThemeColor() + .negativeText(R.string.dialog_button_cancel) + .negativeColorRes(R.color.dialog_button_default) + .onNegative { d, _ -> d.dismiss() } + .positiveText(R.string.dialog_button_next_step) + .positiveColorRes(R.color.dialog_button_attraction) + .cancelable(false) + .autoDismiss(false) + .build() + } + } + + private fun promptCreateProfileByName( + titleRes: Int? = null, + contentRes: Int? = null, + baseItems: List, + afterCreated: (() -> Unit)?, + ) { + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(titleRes ?: R.string.text_symbols_new_profile) + .apply { contentRes?.let { content(it) } } + .inputType(InputType.TYPE_CLASS_TEXT) + .input(getString(R.string.text_symbols_profile_name_hint), SymbolsConfigStore.getPrefillProfileName(this)) { d, input -> + val name = input?.toString()?.trim().orEmpty() + if (name.isBlank()) return@input + + if (SymbolsConfigStore.isReservedDefaultName(this, name)) { + ViewUtils.showSnack(d.view, R.string.text_symbols_profile_name_cannot_be_default, true) + return@input + } + + val existing = SymbolsConfigStore.listProfiles(this).toSet() + if (existing.contains(name)) { + ViewUtils.showSnack(d.view, R.string.text_symbols_profile_name_conflict, true) + return@input + } + + // Save exactly what baseItems provides (including empty list). + // zh-CN: 严格保存 baseItems 提供的数据 (包含空列表). + SymbolsConfigStore.saveProfile(this, name, baseItems) + SymbolsConfigStore.setActiveProfileName(this, name) + + d.dismiss() + ViewUtils.showToast(this, R.string.text_done) + + // Auto switch to the newly created profile immediately. + // zh-CN: 新建配置后立即自动切换到该配置. + rebuildProfilesSpinnerAndSelect(name) + + afterCreated?.invoke() + } + .widgetThemeColor() + .negativeText(R.string.dialog_button_cancel) + .negativeColorRes(R.color.dialog_button_default) + .onNegative { d, _ -> d.dismiss() } + .positiveText(R.string.dialog_button_confirm) + .positiveColorRes(R.color.dialog_button_attraction) + .cancelable(false) + .autoDismiss(false) + .build() + } + } + + private fun promptDeleteProfile() { + if (currentProfileName == SymbolsConfigStore.PROFILE_DEFAULT_ID) { + ViewUtils.showToast(this, R.string.text_cannot_delete_default, true) + return + } + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_prompt) + .content(getString(R.string.text_symbols_delete_profile_confirm, currentProfileName)) + .positiveText(R.string.dialog_button_confirm) + .positiveColorRes(R.color.dialog_button_caution) + .negativeText(R.string.dialog_button_cancel) + .onPositive { d, _ -> + SymbolsConfigStore.deleteProfile(this, currentProfileName) + d.dismiss() + recreate() + } + .build() + } + } + + private fun attachDragToReorder() { + val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, + 0 + ) { + + // Drag gesture snapshot marker. + // zh-CN: 拖拽手势快照标记. + private var dragSnapshotPushed: Boolean = false + + override fun isLongPressDragEnabled(): Boolean = false + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + if (!canEditStructureNow()) return + if (dragSnapshotPushed) return + + // Record one snapshot per drag gesture. + // zh-CN: 每次拖拽手势仅记录一次快照. + pushUndoSnapshot() + saveSticky = true + dragSnapshotPushed = true + updateActionButtons() + } + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + // End drag gesture. + // zh-CN: 结束拖拽手势. + dragSnapshotPushed = false + + updateToggleAllButton() + updateActionButtons() + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean { + if (!canEditStructureNow()) return false + val from = viewHolder.bindingAdapterPosition + val to = target.bindingAdapterPosition + if (from < 0 || to < 0) return false + + // Do NOT record undo here, it is already recorded once in onSelectedChanged(). + // zh-CN: 不要在此处记录撤销, 已在 onSelectedChanged() 记录一次. + val item = items.removeAt(from) + items.add(to, item) + + adapter.notifyItemMoved(from, to) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit + }) + + helper.attachToRecyclerView(recycler) + itemTouchHelper = helper + } + + private fun commitEditingIfNeeded(reason: CommitReason) { + val s = editing ?: return + + // Ignore focus-lost commits while user scrolling. + // zh-CN: 用户滚动期间忽略焦点丢失提交. + if (reason == CommitReason.FocusLost && recyclerUserScrolling) return + + val pos = s.position + if (pos < 0 || pos >= items.size) { + editing = null + return + } + + val raw = s.editText.text?.toString().orEmpty() + + // Exit edit UI first to avoid flicker when adapter updates. + // zh-CN: 先退出编辑 UI, 避免 adapter 更新造成闪烁. + s.editText.clearFocus() + hideKeyboard(s.editText) + + val hasWhitespace = raw.any { it.isWhitespace() } + + // Empty string: treat as cancel edit. + // zh-CN: 空字符串: 视为放弃编辑. + if (raw.isEmpty()) { + if (s.isNewItem) { + recordChange(markSaveSticky = true) { + items.removeAt(pos) + } + adapter.notifyItemRemoved(pos) + updateToggleAllButton() + updateActionButtons() + } else { + // Restore original content silently. + // zh-CN: 静默恢复原内容. + adapter.notifyItemChanged(pos) + } + + editing = null + return + } + + // Whitespace chars are not allowed: toast + revert or delete. + // zh-CN: 不允许包含空白字符: toast + 回滚或删除. + if (hasWhitespace) { + ViewUtils.showToast(this, R.string.text_symbol_invalid_no_whitespace, true) + + if (s.isNewItem) { + recordChange(markSaveSticky = true) { + items.removeAt(pos) + } + adapter.notifyItemRemoved(pos) + updateToggleAllButton() + updateActionButtons() + } else { + adapter.notifyItemChanged(pos) + } + + editing = null + return + } + + val newText = raw + + // If unchanged: just exit edit. + // zh-CN: 若无变化: 仅退出编辑态. + if (!s.isNewItem && newText == s.originalText) { + adapter.notifyItemChanged(pos) + editing = null + return + } + + // Avoid duplicates: treat as invalid and revert/delete. + // zh-CN: 避免重复: 视为非法并回滚或删除. + val dup = items.anyIndexed { i, it -> i != pos && it.text == newText } + if (dup) { + val msg = getString(R.string.text_symbol_name_conflict_with_value, newText) + ViewUtils.showToast(this, msg, true) + + if (s.isNewItem) { + recordChange(markSaveSticky = true) { + items.removeAt(pos) + } + adapter.notifyItemRemoved(pos) + updateToggleAllButton() + updateActionButtons() + } else { + adapter.notifyItemChanged(pos) + } + + editing = null + return + } + + if (newText != s.originalText) { + recordChange(markSaveSticky = true) { + items[pos] = items[pos].copy(text = newText) + } + adapter.notifyItemChanged(pos) + } + + updateActionButtons() + editing = null + } + + private fun List.anyIndexed(p: (index: Int, item: T) -> Boolean): Boolean { + for (i in indices) { + if (p(i, this[i])) return true + } + return false + } + + private fun showKeyboard(editText: EditText) { + val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager ?: return + editText.post { + imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + } + } + + private fun hideKeyboard(editText: EditText) { + val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager ?: return + imm.hideSoftInputFromWindow(editText.windowToken, 0) + } + + private fun findFirstUniqueUnicodeSymbol(): String { + // Start from U+0021 '!' and find the first code point not present in list. + // zh-CN: 从 U+0021 '!' 开始遍历, 找到列表中不存在的第一个 Unicode 字符. + val existing = items.map { it.text }.toHashSet() + + var cp = 0x21 + val max = 0x10FFFF + + while (cp <= max) { + // Skip surrogate range. + // zh-CN: 跳过代理对区间. + if (cp in 0xD800..0xDFFF) { + cp = 0xE000 + continue + } + + if (cp in 0x007F..0x0390) { + cp = 0x0391 + continue + } + + if (cp == 0x03A2) { + cp += 1 + continue + } + + val s = String(Character.toChars(cp)) + + // Must be valid symbol (non-blank and contains no whitespace). + // zh-CN: 必须满足符号合法性 (非空且不含空白字符). + if (SymbolsConfigStore.isValidSymbolText(s) && !existing.contains(s)) { + return s + } + + cp++ + } + + // Fallback. + // zh-CN: 兜底. + return "!" + } + + @Suppress("DEPRECATION") + private fun startExportJson() { + val name = currentProfileName.ifBlank { "default" } + val i = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + putExtra(Intent.EXTRA_TITLE, "autojs6-symbols-$name.json") + } + startActivityForResult(i, REQ_EXPORT_JSON) + } + + @Suppress("DEPRECATION") + private fun startImportJson() { + val i = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + } + startActivityForResult(i, REQ_IMPORT_JSON) + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode != RESULT_OK) return + val uri = data?.data ?: return + + when (requestCode) { + REQ_EXPORT_JSON -> doExportToUri(uri) + REQ_IMPORT_JSON -> doImportFromUri(uri) + } + } + + private fun doExportToUri(uri: Uri) { + runCatching { + val json = SymbolsConfigStore.exportProfileToJson(this, currentProfileName) + contentResolver.openOutputStream(uri, "wt")?.use { out -> + out.write(json.toString(2).toByteArray(StandardCharsets.UTF_8)) + out.flush() + } ?: error("Cannot open output stream") + ViewUtils.showToast(this, R.string.text_done) + }.onFailure { + it.printStackTrace() + ViewUtils.showToast(this, it.message, true) + } + } + + private fun doImportFromUri(uri: Uri) { + runCatching { + val raw = contentResolver.openInputStream(uri)?.use { it.readBytes() } + ?: error("Cannot open input stream") + val json = JSONObject(String(raw, StandardCharsets.UTF_8)) + val (name, importedItems) = SymbolsConfigStore.importProfileFromJson(json) + + val reservedDefault = SymbolsConfigStore.isReservedDefaultName(this, name) + + // If name is a reserved "default" alias, force conflict flow: + // - show 3 options + // - overwrite disabled + // + // zh-CN: 若导入名是 "默认" 保留字别名, 则强制走冲突流程, 并禁用覆盖. + if (reservedDefault) { + handleImportNameConflictAndSave( + desiredName = name, + importedItems = importedItems, + overwriteEnabled = false, + forceConflict = true, + ) + return@runCatching + } + + handleImportNameConflictAndSave( + desiredName = name, + importedItems = importedItems, + overwriteEnabled = true, + forceConflict = false, + ) + }.onFailure { + it.printStackTrace() + ViewUtils.showToast(this, it.message, true) + } + } + + private fun handleImportNameConflictAndSave( + desiredName: String, + importedItems: List, + overwriteEnabled: Boolean, + forceConflict: Boolean, + ) { + val existing = SymbolsConfigStore.listProfiles(this).toSet() + val conflict = forceConflict || existing.contains(desiredName) + + if (!conflict) { + SymbolsConfigStore.saveProfile(this, desiredName, importedItems) + SymbolsConfigStore.setActiveProfileName(this, desiredName) + ViewUtils.showToast(this, R.string.text_done) + recreate() + return + } + + val options = listOf( + getString(R.string.text_import_strategy_overwrite), + getString(R.string.text_import_strategy_auto_rename), + getString(R.string.text_import_strategy_manual_rename), + ) + + DialogUtils.buildAndShowAdaptive { + val builder = MaterialDialog.Builder(this) + .title(R.string.text_import) + .content(getString(R.string.text_import_name_conflict, desiredName)) + .items(options) + .itemsCallback { d, _, which, _ -> + d.dismiss() + when (which) { + 0 -> { // overwrite + if (!overwriteEnabled) return@itemsCallback + SymbolsConfigStore.saveProfile(this, desiredName, importedItems) + SymbolsConfigStore.setActiveProfileName(this, desiredName) + ViewUtils.showToast(this, R.string.text_done) + recreate() + } + 1 -> { // auto rename + val unique = makeUniqueName(existing, desiredName) + SymbolsConfigStore.saveProfile(this, unique, importedItems) + SymbolsConfigStore.setActiveProfileName(this, unique) + ViewUtils.showToast(this, R.string.text_done) + recreate() + } + 2 -> { // manual rename + promptManualRenameAndImport(existing, desiredName, importedItems) + } + } + } + + if (!overwriteEnabled) { + builder.itemsDisabledIndices(0) + } + + builder.build() + } + } + + private fun promptManualRenameAndImport( + existing: Set, + suggestedName: String, + importedItems: List, + ) { + DialogUtils.buildAndShowAdaptive { + MaterialDialog.Builder(this) + .title(R.string.text_import_strategy_manual_rename) + .inputType(InputType.TYPE_CLASS_TEXT) + .input(getString(R.string.text_symbols_profile_name_hint), suggestedName) { d, input -> + val raw = input?.toString()?.trim().orEmpty() + if (raw.isBlank()) { + ViewUtils.showToast(this, R.string.text_symbols_profile_name_invalid, true) + return@input + } + if (SymbolsConfigStore.isReservedDefaultName(this, raw)) { + ViewUtils.showToast(this, R.string.text_symbols_profile_name_cannot_be_default, true) + return@input + } + if (existing.contains(raw)) { + ViewUtils.showToast(this, R.string.text_symbols_profile_name_conflict, true) + return@input + } + + SymbolsConfigStore.saveProfile(this, raw, importedItems) + SymbolsConfigStore.setActiveProfileName(this, raw) + + d.dismiss() + ViewUtils.showToast(this, R.string.text_done) + recreate() + } + .widgetThemeColor() + .positiveText(R.string.dialog_button_confirm) + .positiveColorRes(R.color.dialog_button_attraction) + .negativeText(R.string.dialog_button_cancel) + .negativeColorRes(R.color.dialog_button_default) + .autoDismiss(false) + .build() + } + } + + private fun makeUniqueName(existing: Set, base: String): String { + if (!existing.contains(base)) return base + var i = 2 + while (true) { + val candidate = "$base ($i)" + if (!existing.contains(candidate)) return candidate + i++ + } + } + + private class SymbolsAdapter( + private val data: List, + private val onToggle: (pos: Int, enabled: Boolean) -> Unit, + private val onAdd: (pos: Int) -> Unit, + private val onDelete: (pos: Int) -> Unit, + private val onBeginEdit: (EditingSession) -> Unit, + private val onEditFocusChanged: (EditingSession, hasFocus: Boolean) -> Unit, + private val onRequestDrag: (RecyclerView.ViewHolder) -> Unit, + ) : RecyclerView.Adapter() { + + // Pending edit request for a position. + // zh-CN: 指定 position 的待触发编辑请求. + private var pendingEdit: PendingEdit? = null + + private data class PendingEdit( + val position: Int, + val isNewItem: Boolean, + ) + + fun requestEditAt(position: Int, isNewItem: Boolean) { + pendingEdit = PendingEdit(position = position, isNewItem = isNewItem) + notifyItemChanged(position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val v = android.view.LayoutInflater.from(parent.context).inflate(R.layout.item_symbol_config, parent, false) + return VH(v) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + val item = data[position] + + // Normal view. + // zh-CN: 普通展示态. + holder.tvSymbol.text = item.text + + // Default state: show text, hide editor. + // zh-CN: 默认态: 显示文本, 隐藏编辑器. + holder.tvSymbol.visibility = View.VISIBLE + holder.symbolEditPanel.visibility = View.GONE + + holder.cbEnabled.setOnCheckedChangeListener(null) + holder.cbEnabled.isChecked = item.enabled + holder.cbEnabled.setOnCheckedChangeListener { _, isChecked -> + val pos = holder.bindingAdapterPosition + if (pos >= 0) onToggle(pos, isChecked) + } + + holder.ivAdd.setOnClickListener { + val pos = holder.bindingAdapterPosition + if (pos >= 0) onAdd(pos) + } + + holder.ivDelete.setOnClickListener { + val pos = holder.bindingAdapterPosition + if (pos >= 0) onDelete(pos) + } + holder.ivDelete.setOnLongClickListener(null) + + holder.tvSymbol.setOnClickListener { + val pos = holder.bindingAdapterPosition + if (pos < 0) return@setOnClickListener + enterEditMode(holder = holder, position = pos, isNewItem = false) + } + + holder.tvSymbol.setOnLongClickListener { + onRequestDrag(holder) + true + } + + holder.ivDrag.setOnLongClickListener { + onRequestDrag(holder) + true + } + + // Apply pending edit. + // zh-CN: 应用待触发编辑请求. + val p = pendingEdit + if (p != null && p.position == position) { + pendingEdit = null + enterEditMode(holder = holder, position = position, isNewItem = p.isNewItem) + } + } + + private fun enterEditMode(holder: VH, position: Int, isNewItem: Boolean) { + val item = data.getOrNull(position) ?: return + + holder.tvSymbol.visibility = View.GONE + holder.symbolEditPanel.visibility = View.VISIBLE + + holder.etSymbol.onFocusChangeListener = null + holder.etSymbol.setOnEditorActionListener(null) + + holder.etSymbol.setText(item.text) + holder.etSymbol.setSelection(0, holder.etSymbol.text?.length ?: 0) + + val session = EditingSession( + position = position, + originalText = item.text, + editText = holder.etSymbol, + isNewItem = isNewItem, + ) + + holder.etSymbol.setOnFocusChangeListener { _, hasFocus -> + onEditFocusChanged(session, hasFocus) + } + + holder.etSymbol.setOnEditorActionListener { _, actionId, event -> + val imeDone = actionId == android.view.inputmethod.EditorInfo.IME_ACTION_DONE + val enterDown = event?.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_ENTER + if (imeDone || enterDown) { + // Clear focus to trigger auto-commit. + // zh-CN: 清除焦点以触发自动提交. + holder.etSymbol.clearFocus() + true + } else { + false + } + } + + onBeginEdit(session) + } + + override fun getItemCount(): Int = data.size + + class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { + val cbEnabled: CheckBox = itemView.findViewById(R.id.cbEnabled) + + val tvSymbol: TextView = itemView.findViewById(R.id.tvSymbol) + + val symbolEditPanel: View = itemView.findViewById(R.id.symbolEditPanel) + val etSymbol: EditText = itemView.findViewById(R.id.etSymbol) + + val ivAdd: ImageView = itemView.findViewById(R.id.ivAdd) + val ivDrag: ImageView = itemView.findViewById(R.id.ivDrag) + val ivDelete: ImageView = itemView.findViewById(R.id.ivDelete) + } + } + + private data class ProfileEntry( + val internalName: String, + val displayName: String, + ) + + private enum class CommitReason { + ToolbarAction, + ItemAction, + FocusLost, + ScrollIdle, + SwitchEditor, + SwitchProfile, + LeavingScreen, + } + + private data class EditingSession( + val position: Int, + val originalText: String, + val editText: EditText, + val isNewItem: Boolean, + ) + + companion object { + private const val REQ_EXPORT_JSON = 1001 + private const val REQ_IMPORT_JSON = 1002 + } +} diff --git a/app/src/main/res/layout/activity_symbols_settings.xml b/app/src/main/res/layout/activity_symbols_settings.xml new file mode 100644 index 00000000..3bd2556d --- /dev/null +++ b/app/src/main/res/layout/activity_symbols_settings.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_symbols_toolbar.xml b/app/src/main/res/layout/fragment_symbols_toolbar.xml new file mode 100644 index 00000000..b891f822 --- /dev/null +++ b/app/src/main/res/layout/fragment_symbols_toolbar.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_symbol_config.xml b/app/src/main/res/layout/item_symbol_config.xml new file mode 100644 index 00000000..be349af9 --- /dev/null +++ b/app/src/main/res/layout/item_symbol_config.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_symbols_config_settings_more.xml b/app/src/main/res/menu/menu_symbols_config_settings_more.xml new file mode 100644 index 00000000..57e21fb8 --- /dev/null +++ b/app/src/main/res/menu/menu_symbols_config_settings_more.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_symbols_options.xml b/app/src/main/res/menu/menu_symbols_options.xml new file mode 100644 index 00000000..c0e913e4 --- /dev/null +++ b/app/src/main/res/menu/menu_symbols_options.xml @@ -0,0 +1,17 @@ + + + + + + \ 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 cf4ccdf5..8227cabe 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -119,6 +119,7 @@ تخلَّ إيقاف إيقاف الاتصال + اضافة متقدم تصحيح العنوان @string/text_back @@ -133,6 +134,7 @@ نسخ بادئة افتراضية تفاصيل + تجاهل رفض متصفح تفاصيل @@ -144,6 +146,7 @@ المدير تصغير أكثر + التالي فتح لوحة الألوان معاينة يترك @@ -153,6 +156,7 @@ جلب إعادة المحاولة يحفظ + حفظ ومتابعة حفظ باسم اعدادات النظام النظام @@ -484,6 +488,7 @@ لا مثبت APK هناك حاجة إلى أداة ADB إضافة مكتبة ألوان + اضافة رمز اسم مستعار الاسم المستعار لا يمكن أن يكون فارغًا كلمة مرور الاسم المستعار @@ -553,6 +558,7 @@ نجح بناء يلغي إلغاء + لا يمكن حذف ملف التعريف الافتراضي لا يمكن قراءة الملف الترتيب الحزمة @@ -640,6 +646,7 @@ جارٍ النسخ... رمز الدولة (XX) رمز الدولة يجب أن يكون حرفين كبيرين + اعدادات الصفحة الحالية النسخة الحالية مهام يومية تاريخ @@ -656,8 +663,10 @@ يخرج خطوة أكثر تقصير + الاعدادات الافتراضية مخزن المفاتيح الافتراضي بادئة افتراضية + لا يمكن الكتابة فوق الملف الافتراضي، يمكنك انشاء ملف تعريف باسم مستعار مدة التأخير حذف حذف الكل @@ -667,6 +676,7 @@ هل تريد حذف هذه المراجعة نهائيا? جارٍ الحذف... الوصف + الغاء التحديد الوجهة تفاصيل تفاصيل المطور قيد التطوير @@ -709,6 +719,7 @@ البريد الإلكتروني البريد الإلكتروني لا يمكن أن يكون فارغًا تنسيق البريد الإلكتروني غير صالح + اعدادات فارغة لا ملاحظة إطلاق تمكين خدمة إمكانية الوصول تمكين خدمة إمكانية الوصول مع الوصول إلى الجذر تلقائيًا @@ -728,6 +739,8 @@ مخرج توسيع الكل يصدّر + تصدير الكل + تصدير المحدد تصدير تمديد كائنات JavaScript المدمجة التمدد @@ -808,7 +821,11 @@ تحديثات تجاهل يستورد استيراد مكتبة ألوان + يوجد ملف تعريف بالاسم \"%1$s\" بالفعل، اختر طريقة الاستيراد: استيراد السيناريو + اعادة تسمية تلقائيا + اعادة تسمية يدويا + استبدال الملف الموجود نجح الاستيراد استيراد إلى \"نصوص بلدي\" في تَقَدم @@ -827,6 +844,7 @@ تمت إزالة حرف غير صالح اسم الحزمة غير صالح مشروع غير صالح + عكس التحديد عنوان IP \"%1$s\" له استخدام خاص (loopback/broadcast/multicast/reserved/...)، لذلك قد لا يمكن استخدامه للاتصال بالخادم الهدف. لم يتم العثور على نسخة أحدث استجابة @@ -857,6 +875,7 @@ فحص التصميم ... تراخيص المصادر المفتوحة جارٍ تجربة حل بديل... + اكتمل التحميل جار التحميل... تحديد لون الثيم الحالي سجل @@ -918,6 +937,8 @@ غير الجذر لا الوصول إلى الجذر لا توجد نصوص للتوقف عن الجري + لم يتم العثور على نتائج لـ:\n\n%1$s + الاعدادات الحالية فارغة.\nاضغط هنا لانشاء رمز. لا يوجد سجل للاصدارات لم تمنح غير مثبّت @@ -1099,6 +1120,7 @@ عنوان URL لمستودع امتداد VSCode إرسال نجح يتطلب إصدار Android OS %s (API %s) ولكن الحالي هو %s (API %s) + اعادة ضبط إعادة تعيين كلمة المرور نجحت إعادة تعيين إعادة تعيين في البداية @@ -1141,6 +1163,7 @@ خدمة التشغيل احفظ حفظ وخروج + حفظ الاعدادات باسم احفظ حفظ في المحرّك @@ -1153,6 +1176,8 @@ البحث عن لون مساعدة البحث يختار + اختر قالب اعدادات + تحديد الكل في حالة وجود مقطع (موجود) حتى تجد واحدة (Findone) حتى تجد كل (توتيد) @@ -1166,6 +1191,7 @@ وضع الخادم خدمة إدارة الخدمات + اختر اسما للاعدادات الجديدة تعيين كدليل العمل ضبط نقطة توقف إعدادات @@ -1210,6 +1236,16 @@ التبديل إلى التخطيط التقليدي التبديل إلى التخطيط الجديد تبديل النافذة + لا يمكن ان تحتوي الرموز على مسافات بيضاء + الرمز \"%1$s\" موجود بالفعل + هل تريد حذف ملف التعريف \"%1$s\"? + ملف تعريف جديد + ملف تعريف الرموز + @string/text_default + لا يمكن استخدام \"Default\" كاسم لملف التعريف + اسم ملف التعريف موجود بالفعل + اسم الملف + لا يمكن ترك اسم ملف التعريف فارغا إعدادات الرموز مطور مصممة خصيصا مهمة @@ -1222,6 +1258,7 @@ محرك جدولة المهام المؤقتة توقيت قيد الاختيار + تبديل الكل أدوات الحجم: %1$s قد يتم تلقائيًا إخفاء رمز المشغّل بخلفية شفافة أو إضافة خلفية إليه بواسطة النظام, لذلك قد يختلف التأثير الفعلي بين الأجهزة المختلفة @@ -1292,4 +1329,6 @@ اكتب إعدادات الأمان كتابة إعدادات النظام النوافذ المنبثقة في الخلفية + لم يتم حفظ الاعدادات. هل تريد المتابعة? + لم يتم حفظ الاعدادات. هل تريد الخروج? \ 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 85df798a..0e99174e 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -114,6 +114,7 @@ Abandon Abort Abort + Add Advanced Amend @string/text_back @@ -128,6 +129,7 @@ Copy path Def prefix Details + Discard changes Dismiss Browser Details @@ -139,6 +141,7 @@ Manager Minimize More + Next Palette Preview Quit @@ -148,6 +151,7 @@ Retrieve Retry Save + Save and continue Save as System settings System @@ -479,6 +483,7 @@ No APK installer ADB tool is needed Add a color library + Add a symbol alias Alias cannot be empty Alias Password @@ -548,6 +553,7 @@ Build succeeded Cancel Cancel + Default profile cannot be deleted Cannot read file Order Package @@ -635,6 +641,7 @@ Copying... Country Code (XX) Country code must be two capital letters + Current page configuration Current version Daily task Date @@ -651,8 +658,10 @@ Step out Step over Default + Deafult configuration Default Keystore Default prefix + Default profile cannot be overwritten, you can create a new aliased profile Delay time Delete Delete All @@ -662,6 +671,7 @@ Delete this revision permanently? Deleting... Description + Deselect all Destination Details Developer details is under development @@ -704,6 +714,7 @@ E-mail E-mail cannot be empty Invalid e-mail format + Empty configuration No release note Enable accessibility service Enable accessibility service with root access automatically @@ -723,6 +734,8 @@ Exit Expand all Export + Export all + Export selected Exported Extending JavaScript build-in objects Extensibility @@ -803,7 +816,11 @@ Ignored updates Import Import a color library + A profile named \"%1$s\" already exists, please choose an import strategy: Import script(s) + Auto rename + Manual rename + Overwrite existing profile Import succeeded Import to \"my scripts\" In progress @@ -822,6 +839,7 @@ Invalid character is removed Invalid package name Invalid project + Invert selection IP address \"%1$s\" has a special purpose (loopback/broadcast/multicast/reserved/...), so it may not be usable for connecting to the target server. No newer version found Feedback @@ -852,6 +870,7 @@ Inspecting layout... Open Sources Licenses Trying a fallback solution... + Loading completed Loading... Locate current theme color Log @@ -913,6 +932,8 @@ Non-root No root access No scripts to stop running + No results found for:\n\n%1$s + Current configuration is empty.\nTap here to create a symbol. No version history Not granted Not installed @@ -1094,6 +1115,7 @@ Repository URL of VSCode extension Submit succeeded Requires Android OS version %s (API %s) but current is %s (API %s) + Reset Reset password Reset succeeded Reset initially @@ -1136,6 +1158,7 @@ Running service Save Save and exit + Save configuration as Save Save to Backend @@ -1148,6 +1171,8 @@ Search color Search help Select + Select a configuration template + Select all If clip exists (exists) Until find one (findOne) Until find all (untilFind) @@ -1161,6 +1186,7 @@ Server mode Service Service management + Set a name for the new configuration Set as working dir Set a breakpoint Settings @@ -1205,6 +1231,16 @@ Switch to legacy layout Switch to new layout Switch window + Symbols cannot contain whitespace characters + Symbol \"%1$s\" already exists + Delete profile \"%1$s\"? + New profile + Symbols profile + @string/text_default + Cannot use \"Default\" as profile name + Profile name already exists + Profile name + Profile name cannot be empty Symbols settings Tailor-made developer Task @@ -1217,6 +1253,7 @@ Timed task scheduling engine Timing To be chosen + Toggle all Tools Size: %1$s The transparent background launcher icon may be automatically masked or given a background by the system, so the actual effect may vary across different devices @@ -1287,4 +1324,6 @@ Write security settings Write system settings Display pop-up windows while running in the background + The settings has not been saved, are you sure to continue the operation? + The settings has not been saved, are you sure to exit? \ 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 223de638..d36c2fae 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -117,6 +117,7 @@ Abandonar Abortar Interrumpir conexión + Anadir Avanzado Corregir dirección @string/text_back @@ -131,6 +132,7 @@ Copiar Prefijo def Detalles + Descartar Descartar Navegador Detalles @@ -142,6 +144,7 @@ Administrador Minimizar Más + Siguiente Abrir paleta Vista previa Salir @@ -151,6 +154,7 @@ Obtener Reintentar Guardar + Guardar y continuar Guardar como Configuración del sistema Sistema @@ -482,6 +486,7 @@ No hay instalador de APK Se necesita la herramienta ADB Agregar biblioteca de colores + Anadir un simbolo alias El alias no puede estar vacío Contraseña del alias @@ -551,6 +556,7 @@ Construir con éxito Cancelar Cancel + No se puede eliminar el perfil predeterminado No se puede leer el archivo Orden Paquete @@ -638,6 +644,7 @@ Copiando... Código de país (XX) El código del país debe ser de dos letras mayúsculas + Configuracion de la pagina actual Versión actual Tarea diaria Fecha @@ -654,8 +661,10 @@ Salir Pasar por encima Por defecto + Configuracion predeterminada Almacenamiento de claves predeterminado Prefijo predeterminado + El perfil predeterminado no se puede sobrescribir; puedes crear un perfil con alias Tiempo de retraso Borrar Eliminar todo @@ -665,6 +674,7 @@ ¿Eliminar esta revision de forma permanente? Eliminando... Descripción + Deseleccionar todo Destino Detalles Los detalles del desarrollador están en desarrollo @@ -707,6 +717,7 @@ Correo electrónico El correo electrónico no puede estar vacío Formato de correo electrónico no válido + Configuracion vacia No hay nota de publicación Habilitar el servicio de accesibilidad Habilitar el servicio de accesibilidad con acceso root automáticamente @@ -726,6 +737,8 @@ Salir de Expandir todo Exportar + Exportar todo + Exportar seleccionados Exportado Extensión de los objetos incorporados de JavaScript Extensibilidad @@ -806,7 +819,11 @@ Ignorar actualizaciones Importar Importar biblioteca de colores + Ya existe un perfil llamado \"%1$s\". Elige un metodo de importacion: Importar script(s) + Renombrar automaticamente + Renombrar manualmente + Sobrescribir el perfil existente Importar con éxito Importar a \"mis scripts\" En curso @@ -825,6 +842,7 @@ Carácter inválido ha sido removido Nombre de paquete no válido Proyecto no válido + Invertir seleccion La dirección IP \"%1$s\" tiene un propósito especial (loopback/broadcast/multicast/reserved/...), por lo que es posible que no pueda usarse para conectarse al servidor de destino. No se ha encontrado una versión más reciente Comentarios @@ -855,6 +873,7 @@ Inspeccionando el diseño... Licencias de fuentes abiertas Intentando una solución alternativa... + Carga completada Cargando... Localizar color del tema actual Registrar @@ -916,6 +935,8 @@ No root Sin acceso root No hay scripts que dejen de ejecutarse + No se encontraron resultados para:\n\n%1$s + La configuracion actual esta vacia.\nToca aqui para crear un simbolo. Sin historial de versiones No se concede No instalado @@ -1097,6 +1118,7 @@ URL del repositorio de la extensión VSCode Enviar con éxito Requiere la versión del sistema operativo Android %s (API %s) pero la actual es %s (API %s) + Restablecer Restablecer contraseña Restablecer con éxito Restablecer inicialmente @@ -1139,6 +1161,7 @@ Ejecutar servicio Guardar Guardar y salir + Guardar configuracion como Guarda Guardar en Motor @@ -1151,6 +1174,8 @@ Buscar color Ayuda de búsqueda Seleccione + Selecciona una plantilla de configuracion + Seleccionar todo Si el clip existe (exists) Hasta encontrar uno (findOne) Hasta encontrar todos (untilFind) @@ -1164,6 +1189,7 @@ Modo servidor Servicio Gestión de servicios + Pon un nombre a la nueva configuracion Como directorio de trabajo Establecer un punto de interrupción Configuración @@ -1208,6 +1234,16 @@ Cambiar a diseño antiguo Cambiar a nuevo diseño Cambiar ventana + Los simbolos no pueden contener espacios en blanco + El simbolo \"%1$s\" ya existe + ¿Eliminar el perfil \"%1$s\"? + Nuevo perfil + Perfil de simbolos + @string/text_default + No se puede usar \"Predeterminado\" como nombre del perfil + El nombre del perfil ya existe + Nombre del perfil + El nombre del perfil no puede estar vacio Config de símbolos Desarrollador a medida Tarea @@ -1220,6 +1256,7 @@ Motor de programación de tareas temporizadas Cronometraje Por elegir + Alternar todo Herramientas Tamano: %1$s El ícono de iniciador con fondo transparente puede ser enmascarado automáticamente o recibir un fondo por parte del sistema, por lo que el efecto real puede variar entre dispositivos @@ -1290,4 +1327,6 @@ Escribir la configuración de seguridad Escribir la configuración del sistema Ventanas emergentes en segundo plano + La configuracion no se ha guardado. ¿Seguro que quieres continuar? + La configuracion no se ha guardado. ¿Seguro que quieres salir? \ 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 070b0779..dba43503 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -117,6 +117,7 @@ Abandonner Interrompre Interrompre la connexion + Ajouter Avancé Corriger l\'adresse @string/text_back @@ -131,6 +132,7 @@ Copier Préfixe def Details + Ignorer Quitter Navigateur Détails @@ -142,6 +144,7 @@ Gestionnaire Réduire Plus + Suivant Palette Apercu Quit @@ -151,6 +154,7 @@ Récupérer Retourner Enregistrer + Enregistrer et continuer Enregistrer sous Paramètres du système Système @@ -482,6 +486,7 @@ Pas d\'installateur d\'APK L\'outil ADB est nécessaire Ajouter une bibliothèque de couleurs + Ajouter un symbole alias L\'alias ne peut pas être vide Mot de passe de l\'alias @@ -551,6 +556,7 @@ Construction réussie Annulation Annul + Le profil par defaut ne peut pas etre supprime Impossible de lire le fichier Ordre Paquet @@ -638,6 +644,7 @@ Copie... Code du pays (XX) Le code du pays doit être composé de deux lettres majuscules + Config de la page Version actuelle Tâche quotidienne Date @@ -654,8 +661,10 @@ Step out Step over Default + Config par defaut Magasin de clés par défaut Préfixe par défaut + Le profil par defaut ne peut pas etre ecrase; vous pouvez creer un profil alias Délai Suppression Tout supprimer @@ -665,6 +674,7 @@ Supprimer definitivement cette revision? Suppression... Description + Tout deselectionner Destination Détails Les détails du développeur sont en cours de développement @@ -707,6 +717,7 @@ E-mail L\'e-mail ne peut être vide Format d\'e-mail invalide + Config vide Pas de release note Activer le service d\'accessibilité Activer le service d\'accessibilité avec accès root automatiquement. @@ -726,6 +737,8 @@ Exit Tout développer Export + Tout exporter + Exporter la selection Exporté Extension des objets intégrés de JavaScript Extensibilité @@ -806,7 +819,11 @@ Ignorer les mises à jour Importer Importer une bibliothèque de couleurs + Un profil nomme \"%1$s\" existe deja, choisissez une methode d\'import: Importer un ou des scripts + Renommage auto + Renommage manuel + Ecraser le profil existant Importation réussie Importation vers \"mes scripts\" En cours @@ -825,6 +842,7 @@ Caractère invalide est supprimé Nom de paquet non valide Projet non valide + Inverser L\'adresse IP \"%1$s\" a un usage particulier (loopback/broadcast/multicast/reserved/...), il est donc possible qu\'elle ne puisse pas être utilisée pour se connecter au serveur cible. Pas de version plus récente trouvée Réaction @@ -855,6 +873,7 @@ Inspecter la mise en page... Licences Open Sources Tentative d\'une solution de secours... + Chargement termine Chargement... Localiser la couleur du thème actuel Journal @@ -916,6 +935,8 @@ Non-root Aucun accès root Pas de scripts à arrêter de fonctionner + Aucun resultat pour:\n\n%1$s + La config actuelle est vide.\nAppuyez ici pour creer un symbole. Aucun historique des versions Non accordée Non installé @@ -1097,6 +1118,7 @@ URL du dépôt de l\'extension VSCode Submission réussie Requiert Android OS version %s (API %s) mais la version actuelle est %s (API %s) + Reinitialiser Réinitialiser le mot de passe Réinitialisation réussie Réinitialisation initiale @@ -1139,6 +1161,7 @@ Service en cours d\'exécution Enregistrer Enregistrer et quitter + Enregistrer la config sous Enreg Save to Moteur @@ -1151,6 +1174,8 @@ Rechercher une couleur Aide à la recherche Select + Choisir un modele de config + Tout selectionner Si le clip existe (exists) Until find one (findOne) Until find all (untilFind) @@ -1164,6 +1189,7 @@ Mode serveur Service Gestion des services + Nommer la nouvelle config Comme répertoire de travail Définir un point d\'arrêt Réglages @@ -1208,6 +1234,16 @@ Passer à l\'ancienne disposition Passer à la nouvelle disposition Changer de fenêtre + Les symboles ne peuvent pas contenir d\'espaces + Le symbole \"%1$s\" existe deja + Supprimer le profil \"%1$s\"? + Nouveau profil + Profil de symboles + @string/text_default + Impossible d\'utiliser \"Par defaut\" comme nom de profil + Le nom du profil existe deja + Nom du profil + Le nom du profil ne peut pas etre vide Param des symboles Développeur sur mesure Task @@ -1220,6 +1256,7 @@ Moteur d\'ordonnancement des tâches planifiées Timing A choisir + Basculer tout Outils Taille: %1$s L\'icône du lanceur à fond transparent peut être automatiquement masquée ou recevoir un fond par le système, de sorte que l\'effet réel peut varier selon les appareils @@ -1290,4 +1327,6 @@ Écrire les paramètres de sécurité. Écrire les paramètres système Fenêtres contextuelles en arrière-plan + Les parametres ne sont pas enregistres. Continuer? + Les parametres ne sont pas enregistres. Quitter? \ 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 41ea981c..4499c7f6 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -118,6 +118,7 @@ 放棄 中止 接続を中止 + 追加 詳細 アドレスを修正 @string/text_back @@ -132,6 +133,7 @@ コピー デフォ前置 詳細 + 破棄 削除 ブラウザ 詳細 @@ -143,6 +145,7 @@ マネージャー 最小化 詳細 + 次へ パレットを開く プレビュー 終了する @@ -152,6 +155,7 @@ 取得 再試行 保存する + 保存して続行 名前を付けて保存 システム設定 システム @@ -483,6 +487,7 @@ APK インストーラーなし ADB ツールが必要 カラーライブラリを追加 + 記号を追加 別名 エイリアスは空にできません エイリアスのパスワード @@ -552,6 +557,7 @@ ビルドに成功しました 取消 取消 + デフォルトは削除できません ファイルを読めません 順序 パッケージ @@ -639,6 +645,7 @@ コピー中... 国コード (XX) 国コードは 2 文字の大文字でなければなりません + 現在のページ設定 現在のバージョン デイリータスク 日付 @@ -655,8 +662,10 @@ ステップアウト ステップオーバー デフォルト + デフォルト設定 デフォルトのキーストア デフォルトのプレフィックス + デフォルトは上書きできません. 別名のプロファイルを新規作成できます 遅延時間 削除 すべて削除 @@ -666,6 +675,7 @@ この履歴を完全に削除しますか? 削除中... 説明 + 全て解除 宛先 詳細 デベロッパーの詳細については, 現在開発中です @@ -708,6 +718,7 @@ 電子メール 電子メールを空にすることはできません 電子メールの形式が無効です + 空の設定 リリースノートなし アクセシビリティサービスを有効にする ルートアクセスでアクセシビリティサービスを自動的に有効にする @@ -727,6 +738,8 @@ 終了 すべて展開する 輸出 + 全てエクスポート + 選択をエクスポート エクスポートされました JavaScript 組み込みオブジェクトの拡張 拡張性 @@ -807,7 +820,11 @@ アップデートを無視する インポート カラーライブラリをインポート + 同名のプロファイル \"%1$s\" が存在します. インポート方法を選択してください: インポートスクリプト + 自動リネーム + 手動リネーム + 既存のプロファイルを上書き インポートに成功しました インポート先: 作業ディレクトリ 進行中 @@ -826,6 +843,7 @@ 無効な文字が削除されました パッケージ名が無効です プロジェクトが無効です + 反転 IP アドレス \"%1$s\" は特殊用途 (loopback/broadcast/multicast/reserved/...) のため, 接続先サーバーへの接続に使用できない可能性があります. 新しいバージョンが見つかりません フィードバック @@ -856,6 +874,7 @@ レイアウトの検査中... オープンソースライセンス バックアップ策を試しています... + 読み込み完了 読み込み中... 現在のテーマカラーを特定 ログ @@ -917,6 +936,8 @@ 非ルート ルートアクセス不可 実行を停止するスクリプトなし + 一致する項目が見つかりません:\n\n%1$s + 現在の設定は空です.\nここをタップして記号を作成します. バージョン履歴なし 許可されない 未インストール @@ -1098,6 +1119,7 @@ VSCode 拡張機能のリポジトリ URL 送信に成功しました Android OS バージョン %s (API %s) が必要ですが, 現在のバージョンは %s (API %s)です + リセット パスワードのリセット リセット成功 初期リセット @@ -1140,6 +1162,7 @@ サービスの実行 保存する 保存して終了 + 設定を別名で保存 保存 保存先 エンジン @@ -1152,6 +1175,8 @@ 色を検索 検索ヘルプ 選択 + 設定テンプレートを選択 + 全て選択 クリップが存在する場合 (exists) 1 つ見つけるまで (findOne) すべて見つけるまで (untilFind) @@ -1165,6 +1190,7 @@ サーバーモード サービス サービス管理 + 新しい設定に名前を付ける 作業ディレクトリに設定する ブレークポイントの設定 設定 @@ -1209,6 +1235,16 @@ 従来のレイアウトに切り替え 新しいレイアウトに切り替え ウィンドウの切り替え + 記号に空白文字は含められません + 記号 \"%1$s\" は既に存在します + プロファイル \"%1$s\" を削除しますか? + 新規プロファイル + 記号プロファイル + @string/text_default + プロファイル名に \"Default\" は使えません + プロファイル名が既に存在します + プロファイル名 + プロファイル名は空にできません シンボル設定 テーラーメイド開発者 タスク @@ -1221,6 +1257,7 @@ 定時タスクのスケジューリングエンジン タイミング 未選択 + 全て切替 ツール サイズ: %1$s 透過背景ランチャーアイコンはシステムによって自動的にマスクされたり背景が追加されたりする場合があるため, 実際の効果はデバイスによって異なる場合があります @@ -1291,4 +1328,6 @@ セキュリティ設定の書き込み システム設定の書き込み バックグラウンドでのポップアップ表示 + 設定が保存されていません. 続行しますか? + 設定が保存されていません. 終了しますか? \ 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 21133484..64242560 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -119,6 +119,7 @@ 포기 중지 연결 중지 + 추가 고급 주소 수정 @string/text_back @@ -133,6 +134,7 @@ 복사 기본 접두사 세부 + 버리기 닫기 브라우저 자세히 @@ -144,6 +146,7 @@ 관리자 최소화 + 다음 팔레트 열기 미리보기 그만두다 @@ -153,6 +156,7 @@ 가져오기 다시 해 보다 구하다 + 저장 후 계속 다른 이름으로 저장 환경 설정 시스템 @@ -484,6 +488,7 @@ APK 설치 프로그램이 없습니다 ADB 도구가 필요합니다 색상 라이브러리 추가 + 기호 추가 별칭 별칭은 비워둘 수 없습니다 별칭 비밀번호 @@ -553,6 +558,7 @@ 빌드가 성공했습니다 취소 취소 + 기본 프로필은 삭제할 수 없습니다 파일을 읽을 수 없다 순서 패키지 @@ -640,6 +646,7 @@ 복사 중... 국가 코드 (XX) 국가 코드는 두 개의 대문자로 입력해야 합니다 + 현재 페이지 설정 현재 버전 일상적인 작업 날짜 @@ -656,8 +663,10 @@ 나가 한 걸음 더 기본 + 기본 설정 기본 키 저장소 기본 접두사 + 기본 프로필은 덮어쓸 수 없습니다. 별칭 프로필을 새로 만들 수 있습니다 지연 시간 삭제 모두 삭제 @@ -667,6 +676,7 @@ 이 버전을 영구 삭제할까요? 삭제 중... 설명 + 전체 해제 대상 세부 개발자 세부 사항이 개발 중입니다 @@ -709,6 +719,7 @@ 이메일 이메일이 비어질 수 없다 잘못된 이메일 형식 + 빈 설정 릴리스 노트가 없습니다 접근성 서비스를 활성화합니다 루트 액세스를 통해 자동으로 접근성 서비스를 활성화하십시오 @@ -728,6 +739,8 @@ 출구 모두 펼치기 내보내다 + 모두 내보내기 + 선택 항목 내보내기 내보낸 JavaScript 내장 개체 확장 확장성 @@ -808,7 +821,11 @@ 업데이트를 무시했습니다 수입 색상 라이브러리 가져오기 + 이름이 \"%1$s\" 인 프로필이 이미 있습니다. 가져오기 방식을 선택하세요: 스크립트 가져 오기 + 자동 이름 변경 + 수동 이름 변경 + 기존 프로필 덮어쓰기 수입이 성공했습니다 \"내 스크립트\"로 가져 오기 진행 중 @@ -827,6 +844,7 @@ 잘못된 문자가 제거되었습니다 잘못된 패키지 이름 잘못된 프로젝트 + 선택 반전 IP 주소 \"%1$s\" 는 특수 용도 (loopback/broadcast/multicast/reserved/...) 를 가지므로, 대상 서버에 연결하는 데 사용할 수 없을 수도 있습니다. 새로운 버전이 발견되지 않았습니다 피드백 @@ -857,6 +875,7 @@ 레이아웃 검사 ... 오픈 소스 라이센스 대체 솔루션을 시도하는 중... + 로딩 완료 로딩 중... 현재 테마 색상 찾기 통나무 @@ -918,6 +937,8 @@ 뿌리가 아닌 루트 액세스가 없습니다 실행을 멈출 스크립트가 없습니다 + 일치하는 항목이 없습니다:\n\n%1$s + 현재 설정이 비어 있습니다.\n여기를 눌러 기호를 만드세요. 버전 기록 없음 부여되지 않았습니다 설치되지 않음 @@ -1099,6 +1120,7 @@ VSCode 확장의 리포지토리 URL 제출 성공 Android OS 버전 %s (API %s) 가 필요하지만 현재는 %s (API %s) 입니다. + 초기화 암호를 재설정 재설정이 성공했습니다 처음에 재설정하십시오 @@ -1141,6 +1163,7 @@ 실행 서비스 구하다 저장하고 종료 + 다른 이름으로 저장 구하다 저장 엔진 @@ -1153,6 +1176,8 @@ 색상 검색 검색 도움말 고르다 + 설정 템플릿 선택 + 전체 선택 클립이 존재하는 경우 (exists) 하나를 찾을 때까지 (findOne) 모든 것을 찾을 때까지 (untilFind) @@ -1166,6 +1191,7 @@ 서버 모드 서비스 서비스 관리 + 새 설정 이름 지정 작업 디렉토리로 중단 점을 설정하십시오 설정 @@ -1210,6 +1236,16 @@ 기존 레이아웃으로 전환 새 레이아웃으로 전환 창 전환 + 기호에는 공백 문자를 포함할 수 없습니다 + 기호 \"%1$s\" 이 (가) 이미 존재합니다 + 프로필 \"%1$s\" 을 (를) 삭제할까요? + 새 프로필 + 기호 프로필 + @string/text_default + 프로필 이름으로 \"Default\" 를 사용할 수 없습니다 + 프로필 이름이 이미 존재합니다 + 프로필 이름 + 프로필 이름은 비워 둘 수 없습니다 기호 설정 맞춤형 개발자 @@ -1222,6 +1258,7 @@ 정시 작업 스케줄링 엔진 타이밍 선택 대기 + 전체 전환 도구 크기: %1$s 투명 배경 런처 아이콘은 시스템에 의해 자동으로 마스킹되거나 배경이 추가될 수 있어 장치마다 실제 효과가 다를 수 있습니다 @@ -1292,4 +1329,6 @@ 보안 설정을 작성하십시오 시스템 설정을 작성하십시오 백그라운드 팝업 + 설정이 저장되지 않았습니다. 계속할까요? + 설정이 저장되지 않았습니다. 종료할까요? \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 02284a06..c417337f 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -16,6 +16,7 @@ @color/day_night @color/day_night_full @color/day_night_alpha_70 + @color/day_night_alpha_60 @color/day_night_alpha_50 @color/day_night_alpha_40 @color/day_night_alpha_30 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 537ac3b5..dfcfc728 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -117,6 +117,7 @@ Отказаться Прервать Прервать соединение + Добавить Доп. Исправить адрес @string/text_back @@ -131,6 +132,7 @@ Копировать Префикс деф. Подробности + Отменить Удалить Браузер Детали @@ -142,6 +144,7 @@ Менеджер Свернуть Еще + Далее Открыть палитру Предпросмотр Выйти @@ -151,6 +154,7 @@ Получить Повторная попытка Сохранить + Сохранить и продолжить Сохранить как Системные настройки Система @@ -482,6 +486,7 @@ Нет установщика APK Необходим инструмент ADB Добавить цветовую библиотеку + Добавить символ псевдоним Псевдоним не может быть пустым Пароль псевдонима @@ -551,6 +556,7 @@ Сборка прошла успешно Отменить Отмен + Профиль по умолчанию нельзя удалить Невозможно прочитать файл Порядок Пакет @@ -638,6 +644,7 @@ Копирование... Код страны (XX) Код страны должен состоять из двух заглавных букв + Конфигурация страницы Текущая версия Ежедневная задача Дата @@ -654,8 +661,10 @@ Шаг наружу Шаг за По умолчанию + Конфигурация по умолчанию По умолчанию хранилище ключей Префикс по умолчанию + Профиль по умолчанию нельзя перезаписать; можно создать профиль-алиас Время задержки Удалить Удалить все @@ -665,6 +674,7 @@ Удалить эту ревизию навсегда? Удаление... Описание + Снять выделение Назначение Детали Детали разработчика находятся в стадии разработки @@ -707,6 +717,7 @@ Электронная почта Электронная почта не может быть пустой Неверный формат электронной почты + Пустая конфигурация Отсутствует примечание к релизу Включить службу доступности Включить службу доступности с корневым доступом автоматически @@ -726,6 +737,8 @@ Выйти Развернуть всё Экспорт + Экспортировать все + Экспорт выбранного Экспортировано Расширение встроенных объектов JavaScript Расширяемость @@ -806,7 +819,11 @@ Игнорировать обновления Импорт Импортировать цветовую библиотеку + Профиль с именем \"%1$s\" уже существует. Выберите способ импорта: Импорт скрипта(ов) + Автопереименовать + Переименовать вручную + Перезаписать существующий профиль Импорт удался Импорт в \"мои скрипты\" В процессе выполнения @@ -825,6 +842,7 @@ Недопустимый символ удален Неверное имя пакета Неверный проект + Инвертировать IP-адрес \"%1$s\" имеет специальное назначение (loopback/broadcast/multicast/reserved/...), поэтому он может быть непригоден для подключения к целевому серверу. Более новая версия не найдена Обратная связь @@ -855,6 +873,7 @@ Проверка макета... Лицензии на открытые источники Пробуем запасное решение... + Загрузка завершена Загрузка... Найти текущий цвет темы Журнал @@ -916,6 +935,8 @@ Не root Нет root-доступа Нет скриптов для остановки выполнения + Ничего не найдено по запросу:\n\n%1$s + Текущая конфигурация пуста.\nНажмите здесь, чтобы создать символ. Нет истории версий Не предоставляется Не установлено @@ -1097,6 +1118,7 @@ URL репозитория расширения VSCode Отправить успешно Требуется версия Android OS %s (API %s), но текущая версия %s (API %s) + Сбросить Сброс пароля Сброс выполнен успешно Сброс первоначально @@ -1139,6 +1161,7 @@ Запуск сервиса Сохранить Сохранить и выйти + Сохранить конфигурацию как Сохран Сохранить в Движок @@ -1151,6 +1174,8 @@ Поиск цвета Помощь по поиску Выберите + Выберите шаблон конфигурации + Выбрать все Если клип существует (существует) Пока не найдется один (findOne) Пока не найдем все (untilFind) @@ -1164,6 +1189,7 @@ Режим сервера Сервис Управление службами + Задайте имя для новой конфигурации Как рабочий каталог Установить точку останова Настройки @@ -1208,6 +1234,16 @@ Переключиться на старый макет Переключиться на новый макет Переключить окно + Символы не могут содержать пробельные символы + Символ \"%1$s\" уже существует + Удалить профиль \"%1$s\"? + Новый профиль + Профиль символов + @string/text_default + Нельзя использовать \"По умолчанию\" как имя профиля + Имя профиля уже существует + Имя профиля + Имя профиля не может быть пустым Настройки символов Индивидуальный разработчик Задание @@ -1220,6 +1256,7 @@ Движок планирования таймерных задач Время Выбрать + Переключить все Инструменты Размер: %1$s Значок запуска с прозрачным фоном может быть автоматически замаскирован системой или получить фон, поэтому фактический результат может отличаться на разных устройствах @@ -1290,4 +1327,6 @@ Параметры безопасности записи Запись системных настроек Всплывающие окна в фоне + Настройки не сохранены. Продолжить? + Настройки не сохранены. Выйти? \ 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 efeb93c2..c5c15ab7 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -113,6 +113,7 @@ 放棄 中止 中止連接 + 添加 高級設置 修正地址 @string/text_back @@ -127,6 +128,7 @@ 複製路徑 默認前綴 瞭解更多 + 放棄更改 關閉 瀏覽器下載 異常詳情 @@ -138,6 +140,7 @@ 管理器 最小化 瞭解更多 + 下一步 打開調色盤 預覽 放棄 @@ -147,6 +150,7 @@ 獲取 重試 保存 + 保存並繼續 另存為 系統設置 @string/dialog_button_system_settings @@ -478,6 +482,7 @@ 找不到 APK 安裝器 需要 ADB 命令行工具 添加顏色庫 + 添加一個符號 別名 別名不能為空 別名密碼 @@ -547,6 +552,7 @@ 打包成功 取消 取消 + 默認配置不可刪除 無法讀取文件 次序 包名 @@ -634,6 +640,7 @@ 複製中... 國家代碼 (XX) 國家代碼必須為兩個大寫字母 + 當前頁面配置 當前版本 每日任務 日期 @@ -650,8 +657,10 @@ 跳出 單步 默認 + 默認配置 預設密鑰庫 默認前綴 + 默認配置無法被覆寫, 可新建一個別名配置 延遲時間 刪除 刪除全部 @@ -661,6 +670,7 @@ 是否永久刪除此版本記錄 正在刪除... 描述 + 取消全選 目標 詳情 \"開發者詳情\" 正在開發中... @@ -703,6 +713,7 @@ 郵箱 郵箱不能為空 郵箱格式錯誤 + 空白配置 無版本信息 啓用無障礙服務 使用 root 權限自動啓用無障礙服務 @@ -722,6 +733,8 @@ 直接退出 展開全部 導出 + 導出全部 + 導出選中項 已導出 JavaScript 內置對象擴展 擴展性 @@ -802,7 +815,11 @@ 已忽略更新 導入 導入顏色庫 + 已存在同名配置 \"%1$s\", 請選擇導入方式: 導入腳本文件 + 自動重命名 + 手動重命名 + 覆蓋現有配置 導入成功 導入到 \"我的腳本\" 處理中 @@ -821,6 +838,7 @@ 無效字符已被移除 無效包名 無效項目 + 反選 IP 地址 \"%1$s\" 具備特殊用途 (迴環/廣播/多播/保留/...), 因此可能無法用於連接目標服務器. 已是最新版本 問題反饋 @@ -851,6 +869,7 @@ 佈局分析中... 開源許可信息 正在嘗試備選方案... + 加載完畢 加載中... 定位當前主題色 日誌 @@ -912,6 +931,8 @@ 免 root 無 root 權限 無運行中的腳本 + 未找到匹配項:\n\n%1$s + 當前配置為空.\n點擊此處可新建符號. 無版本歷史 未授予 未安裝 @@ -1093,6 +1114,7 @@ VSCode 插件項目地址 提交成功 要求最低安卓系統版本 %s (API %s), 當前為 %s (API %s) + 重置 重置密碼 重置成功 重置為初始內容 @@ -1135,6 +1157,7 @@ 正在運行的服務 保存 保存並退出 + 配置另存為 保存 保存到 調度引擎 @@ -1147,6 +1170,8 @@ 搜索顏色 搜索幫助 選擇 + 選擇一個配置模版 + 全選 判斷控件存在 (exists) 直到找到一個 (findOne) 直到找到所有 (untilFind) @@ -1160,6 +1185,7 @@ 服務端模式 服務 服務管理 + 為新配置設置一個名稱 用作工作路徑 設置斷點 設置 @@ -1204,6 +1230,16 @@ 切換至傳統佈局 切換至新佈局 切換窗口 + 符號不可包含空白字符 + 符號 \"%1$s\" 已存在 + 是否刪除配置 \"%1$s\" + 新建配置 + 符號配置 + @string/text_default + 不能使用 \"默認\" 作為配置名稱 + 配置名稱已存在 + 配置名稱 + 配置名稱不能為空 符號設置 二次開發者 任務 @@ -1216,6 +1252,7 @@ 定時任務調度引擎 定時 待選擇 + 全選切換 工具 大小: %1$s 透明背景啓動器圖標可能被系統自動添加遮罩或背景, 因此不同設備的實際效果與預期可能不完全一致 @@ -1286,4 +1323,6 @@ 修改安全設置 修改系統設置 後台彈出界面 + 設置尚未保存, 確定要繼續操作嗎 + 設置尚未保存, 確定要退出嗎 \ 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 76dd9c4a..77dc24d7 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -113,6 +113,7 @@ 放棄 中止 中止連線 + 新增 高階設定 修正地址 @string/text_back @@ -127,6 +128,7 @@ 複製路徑 預設字首 瞭解更多 + 放棄更改 關閉 瀏覽器下載 異常詳情 @@ -138,6 +140,7 @@ 管理器 最小化 瞭解更多 + 下一步 開啟調色盤 預覽 放棄 @@ -147,6 +150,7 @@ 獲取 重試 儲存 + 儲存並繼續 另存為 系統設定 @string/dialog_button_system_settings @@ -478,6 +482,7 @@ 找不到 APK 安裝器 需要 ADB 命令列工具 新增顏色庫 + 新增一個符號 別名 別名不能為空 別名密碼 @@ -547,6 +552,7 @@ 打包成功 取消 取消 + 預設配置不可刪除 無法讀取檔案 次序 包名 @@ -634,6 +640,7 @@ 複製中... 國家代碼 (XX) 國家代碼必須是兩個大寫字母 + 當前頁面配置 當前版本 每日任務 日期 @@ -650,8 +657,10 @@ 跳出 單步 預設 + 預設配置 預設密鑰庫 預設字首 + 預設配置無法被覆寫, 可新建一個別名配置 延遲時間 刪除 刪除全部 @@ -661,6 +670,7 @@ 是否永久刪除此版本記錄 正在刪除... 描述 + 取消全選 目標 詳情 \"開發者詳情\" 正在開發中... @@ -703,6 +713,7 @@ 郵箱 郵箱不能為空 郵箱格式錯誤 + 空白配置 無版本資訊 啟用無障礙服務 使用 root 許可權自動啟用無障礙服務 @@ -722,6 +733,8 @@ 直接退出 展開全部 匯出 + 匯出全部 + 匯出選中項 已匯出 JavaScript 內建物件擴充套件 擴充套件性 @@ -802,7 +815,11 @@ 已忽略更新 匯入 匯入顏色庫 + 已存在同名配置 \"%1$s\", 請選擇匯入方式: 匯入指令碼檔案 + 自動重新命名 + 手動重新命名 + 覆蓋現有配置 匯入成功 匯入到 \"我的指令碼\" 處理中 @@ -821,6 +838,7 @@ 無效字元已被移除 無效包名 無效專案 + 反選 IP 地址 \"%1$s\" 具備特殊用途 (迴環/廣播/多播/保留/...), 因此可能無法用於連線目標伺服器. 已是最新版本 問題反饋 @@ -851,6 +869,7 @@ 佈局分析中... 開源許可資訊 正在嘗試備選方案... + 載入完畢 載入中... 定位當前主題色 日誌 @@ -912,6 +931,8 @@ 免 root 無 root 許可權 無執行中的指令碼 + 未找到匹配項:\n\n%1$s + 當前配置為空.\n點選此處可新建符號. 無版本歷史 未授予 未安裝 @@ -1093,6 +1114,7 @@ VSCode 外掛專案地址 提交成功 要求最低安卓系統版本 %s (API %s), 當前為 %s (API %s) + 重置 重置密碼 重置成功 重置為初始內容 @@ -1135,6 +1157,7 @@ 正在執行的服務 儲存 儲存並退出 + 配置另存為 儲存 儲存到 排程引擎 @@ -1147,6 +1170,8 @@ 搜尋顏色 搜尋幫助 選擇 + 選擇一個配置模版 + 全選 判斷控制元件存在 (exists) 直到找到一個 (findOne) 直到找到所有 (untilFind) @@ -1160,6 +1185,7 @@ 服務端模式 服務 服務管理 + 為新配置設定一個名稱 用作工作路徑 設定斷點 設定 @@ -1204,6 +1230,16 @@ 切換至傳統佈局 切換至新佈局 切換視窗 + 符號不可包含空白字元 + 符號 \"%1$s\" 已存在 + 是否刪除配置 \"%1$s\" + 新建配置 + 符號配置 + @string/text_default + 不能使用 \"預設\" 作為配置名稱 + 配置名稱已存在 + 配置名稱 + 配置名稱不能為空 符號設定 二次開發者 任務 @@ -1216,6 +1252,7 @@ 定時任務排程引擎 定時 待選擇 + 全選切換 工具 大小: %1$s 透明背景啟動器圖示可能被系統自動新增遮罩或背景, 因此不同裝置的實際效果與預期可能不完全一致 @@ -1286,4 +1323,6 @@ 修改安全設定 修改系統設定 後臺彈出介面 + 設定尚未儲存, 確定要繼續操作嗎 + 設定尚未儲存, 確定要退出嗎 \ 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 b74ac526..fb7561c8 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -3,6 +3,7 @@ + 构建中... 清理临时文件... 打包中... @@ -113,6 +114,7 @@ 放弃 中止 中止连接 + 添加 高级设置 修正地址 @string/text_back @@ -127,6 +129,7 @@ 复制路径 默认前缀 了解更多 + 放弃更改 关闭 浏览器下载 异常详情 @@ -138,6 +141,7 @@ 管理器 最小化 了解更多 + 下一步 打开调色盘 预览 放弃 @@ -147,6 +151,7 @@ 获取 重试 保存 + 保存并继续 另存为 系统设置 @string/dialog_button_system_settings @@ -478,6 +483,7 @@ 找不到 APK 安装器 需要 ADB 命令行工具 添加颜色库 + 添加一个符号 别名 别名不能为空 别名密码 @@ -547,6 +553,7 @@ 打包成功 取消 取消 + 默认配置不可删除 无法读取文件 次序 包名 @@ -634,6 +641,7 @@ 复制中... 国家代码 (XX) 国家代码必须为两个大写字母 + 当前页面配置 当前版本 每日任务 日期 @@ -650,8 +658,10 @@ 跳出 单步 默认 + 默认配置 默认密钥库 默认前缀 + 默认配置无法被覆写, 可新建一个别名配置 延迟时间 删除 删除全部 @@ -661,6 +671,7 @@ 是否永久删除此版本记录 正在删除... 描述 + 取消全选 目标 详情 \"开发者详情\" 正在开发中... @@ -703,6 +714,7 @@ 邮箱 邮箱不能为空 邮箱格式错误 + 空白配置 无版本信息 启用无障碍服务 使用 root 权限自动启用无障碍服务 @@ -722,6 +734,8 @@ 直接退出 展开全部 导出 + 导出全部 + 导出选中项 已导出 JavaScript 内置对象扩展 扩展性 @@ -802,7 +816,11 @@ 已忽略更新 导入 导入颜色库 + 已存在同名配置 \"%1$s\", 请选择导入方式: 导入脚本文件 + 自动重命名 + 手动重命名 + 覆盖现有配置 导入成功 导入到 \"我的脚本\" 处理中 @@ -821,6 +839,7 @@ 无效字符已被移除 无效包名 无效项目 + 反选 IP 地址 \"%1$s\" 具备特殊用途 (回环/广播/多播/保留/...), 因此可能无法用于连接目标服务器. 已是最新版本 问题反馈 @@ -851,6 +870,7 @@ 布局分析中... 开源许可信息 正在尝试备选方案... + 加载完毕 加载中... 定位当前主题色 日志 @@ -912,6 +932,8 @@ 免 root 无 root 权限 无运行中的脚本 + 未找到匹配项:\n\n%1$s + 当前配置为空.\n点击此处可新建符号. 无版本历史 未授予 未安装 @@ -1093,6 +1115,7 @@ VSCode 插件项目地址 提交成功 要求最低安卓系统版本 %s (API %s), 当前为 %s (API %s) + 重置 重置密码 重置成功 重置为初始内容 @@ -1135,6 +1158,7 @@ 正在运行的服务 保存 保存并退出 + 配置另存为 保存 保存到 调度引擎 @@ -1147,6 +1171,8 @@ 搜索颜色 搜索帮助 选择 + 选择一个配置模版 + 全选 判断控件存在 (exists) 直到找到一个 (findOne) 直到找到所有 (untilFind) @@ -1160,6 +1186,7 @@ 服务端模式 服务 服务管理 + 为新配置设置一个名称 用作工作路径 设置断点 设置 @@ -1204,6 +1231,16 @@ 切换至传统布局 切换至新布局 切换窗口 + 符号不可包含空白字符 + 符号 \"%1$s\" 已存在 + 是否删除配置 \"%1$s\" + 新建配置 + 符号配置 + @string/text_default + 不能使用 \"默认\" 作为配置名称 + 配置名称已存在 + 配置名称 + 配置名称不能为空 符号设置 二次开发者 任务 @@ -1216,6 +1253,7 @@ 定时任务调度引擎 定时 待选择 + 全选切换 工具 大小: %1$s 透明背景启动器图标可能被系统自动添加遮罩或背景, 因此不同设备的实际效果与预期可能不完全一致 @@ -1286,6 +1324,6 @@ 修改安全设置 修改系统设置 后台弹出界面 - 加载完毕 - 未找到匹配项:\n\n%1$s + 设置尚未保存, 确定要继续操作吗 + 设置尚未保存, 确定要退出吗 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 79f48d29..9d36fe0b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -36,6 +36,7 @@ @color/day_night @color/day_night_full @color/day_night_alpha_70 + @color/day_night_alpha_60 @color/day_night_alpha_50 @color/day_night_alpha_40 @color/day_night_alpha_30 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 299aef5b..8cd8ea16 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -385,6 +385,7 @@ Abandon Abort Abort + Add Advanced Amend @string/text_back @@ -399,6 +400,7 @@ Copy path Def prefix Details + Discard changes Dismiss Browser Details @@ -410,6 +412,7 @@ Manager Minimize More + Next Palette Preview Quit @@ -419,6 +422,7 @@ Retrieve Retry Save + Save and continue Save as System Settings System @@ -753,6 +757,7 @@ No APK installer ADB tool is needed Add a color library + Add a symbol alias Alias cannot be empty Alias Password @@ -822,6 +827,7 @@ Build succeeded Cancel Cancel + Default profile cannot be deleted Cannot read file Order Package @@ -909,6 +915,7 @@ Copying... Country Code (XX) Country code must be two capital letters + Current page configuration Current version Daily task Date @@ -925,8 +932,10 @@ Step out Step over Default + Deafult configuration Default Keystore Default prefix + Default profile cannot be overwritten, you can create a new aliased profile Delay time Delete Delete All @@ -936,6 +945,7 @@ Delete this revision permanently? Deleting... Description + Deselect all Destination Details Developer details is under development @@ -978,6 +988,7 @@ E-mail E-mail cannot be empty Invalid e-mail format + Empty configuration No release note Enable accessibility service Enable accessibility service with root access automatically @@ -997,6 +1008,8 @@ Exit Expand all Export + Export all + Export selected Exported Extending JavaScript build-in objects Extensibility @@ -1077,7 +1090,11 @@ Ignored updates Import Import a color library + A profile named \"%1$s\" already exists, please choose an import strategy: Import script(s) + Auto rename + Manual rename + Overwrite existing profile Import succeeded Import to \"my scripts\" In progress @@ -1096,6 +1113,7 @@ Invalid character is removed Invalid package name Invalid project + Invert selection IP address \"%1$s\" has a special purpose (loopback/broadcast/multicast/reserved/...), so it may not be usable for connecting to the target server. No newer version found Feedback @@ -1126,6 +1144,7 @@ Inspecting layout... Open Sources Licenses Trying a fallback solution... + Loading completed Loading... Locate current theme color Log @@ -1187,6 +1206,8 @@ Non-root No root access No scripts to stop running + No results found for:\n\n%1$s + Current configuration is empty.\nTap here to create a symbol. No version history Not granted Not installed @@ -1368,6 +1389,7 @@ Repository URL of VSCode extension Submit succeeded Requires Android OS version %s (API %s) but current is %s (API %s) + Reset Reset password Reset succeeded Reset initially @@ -1410,6 +1432,7 @@ Running service Save Save and exit + Save configuration as Save Save to Backend @@ -1422,6 +1445,8 @@ Search color Search help Select + Select a configuration template + Select all If clip exists (exists) Until find one (findOne) Until find all (untilFind) @@ -1435,6 +1460,7 @@ Server mode Service Service management + Set a name for the new configuration Set as working dir Set a breakpoint Settings @@ -1479,6 +1505,16 @@ Switch to legacy layout Switch to new layout Switch window + Symbols cannot contain whitespace characters + Symbol \"%1$s\" already exists + Delete profile \"%1$s\"? + New profile + Symbols profile + @string/text_default + Cannot use \"Default\" as profile name + Profile name already exists + Profile name + Profile name cannot be empty Symbols settings Tailor-made developer Task @@ -1491,6 +1527,7 @@ Timed task scheduling engine Timing To be chosen + Toggle all Tools Size: %1$s The transparent background launcher icon may be automatically masked or given a background by the system, so the actual effect may vary across different devices @@ -1561,6 +1598,6 @@ Write security settings Write system settings Display pop-up windows while running in the background - Loading completed - No results found for:\n\n%1$s + The settings has not been saved, are you sure to continue the operation? + The settings has not been saved, are you sure to exit? \ No newline at end of file diff --git a/version.properties b/version.properties index 1f0e1464..0bd164a9 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Sun Feb 08 19:50:56 CST 2026 -BUILD_TIME=1770551456669 +#Thu Feb 12 17:13:51 CST 2026 +BUILD_TIME=1770887631678 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=3715 +VERSION_BUILD=3734 VERSION_NAME=6.7.0 Alpha19 VSCODE_EXT_REQUIRED_VERSION=1.0.13