6.7.0 - Alpha20 - 修复部分设备无法正常启用插件的问题

This commit is contained in:
SuperMonster003
2026-02-13 18:40:24 +08:00
parent 994688bf42
commit ff478c0c0c
17 changed files with 341 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
package org.autojs.autojs.core.plugin.center
import android.content.Context
import android.content.pm.ApplicationInfo
import android.graphics.drawable.Drawable
import androidx.core.content.pm.PackageInfoCompat
import kotlinx.coroutines.Dispatchers
@@ -14,6 +15,9 @@ import java.io.File
* Local installed plugin discovery (based on existing PaddleOcrPluginHost.discover).
*
* zh-CN: 本地已安装插件发现 (基于现有的 PaddleOcrPluginHost.discover).
*
* Modified by JetBrains AI Assistant (GPT-5.2-Codex (xhigh)) as of Feb 13, 2026.
* Modified by SuperMonster003 as of Feb 13, 2026.
*/
class InstalledPluginRepository {
@@ -29,6 +33,8 @@ class InstalledPluginRepository {
val lastUpdateTime: Long?,
val icon: Drawable?,
val pluginInfo: PluginInfo?,
val bindError: Throwable?,
val isStopped: Boolean,
)
suspend fun discoverInstalled(context: Context): List<InstalledPlugin> = withContext(Dispatchers.IO) {
@@ -41,6 +47,7 @@ class InstalledPluginRepository {
val appInfo = runCatching { pm.getApplicationInfo(packageName, 0) }.getOrNull()
val appLabel = appInfo?.loadLabel(pm)?.toString()
val icon = appInfo?.loadIcon(pm)
val isStopped = appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_STOPPED) != 0
val pkgInfo = runCatching { pm.getPackageInfo(packageName, 0) }.getOrNull()
val versionName = pkgInfo?.versionName ?: d.pluginInfo?.versionName ?: context.getString(R.string.text_unknown)
@@ -70,6 +77,8 @@ class InstalledPluginRepository {
lastUpdateTime = lastUpdateTime,
icon = icon,
pluginInfo = d.pluginInfo,
bindError = d.error,
isStopped = isStopped,
)
}
}

View File

@@ -14,10 +14,15 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.autojs.autojs.core.plugin.ocr.PaddleOcrPluginHost
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.IntentUtils.startSafely
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs.util.ViewUtils.excludePaddingClippableViewFromBottomNavigationBar
import org.autojs.autojs6.R
import org.autojs.autojs6.databinding.FragmentPluginCenterBinding
@@ -62,8 +67,75 @@ class PluginCenterFragment : Fragment(R.layout.fragment_plugin_center) {
adapter = PluginCenterItemAdapter(object : PluginCenterItemAdapter.Listener {
override fun onToggleEnable(item: PluginCenterItem, enabled: Boolean) {
vm.setEnabled(contextRef, item.packageName, enabled)
item.isEnabled = enabled
if (!enabled) {
vm.setEnabled(contextRef, item.packageName, false)
item.isEnabled = false
return
}
vm.setEnabled(contextRef, item.packageName, true)
item.isEnabled = true
viewLifecycleOwner.lifecycleScope.launch {
val error = runCatching {
PaddleOcrPluginHost.probe(contextRef, item.packageName)
}.exceptionOrNull()
if (error != null && error !is CancellationException) {
if (isAdded) {
val pm = contextRef.packageManager
val launchIntent = pm.getLaunchIntentForPackage(item.packageName)
val wakeIntent = PluginWakeManager.buildWakeIntent(contextRef, item.packageName)
val errorBody = error.message ?: error.toString()
val message = listOf(
contextRef.getString(R.string.text_exception_info) + contextRef.getString(R.string.symbol_colon_with_blank),
errorBody,
contextRef.getString(R.string.text_hint) + contextRef.getString(R.string.symbol_colon_with_blank),
getString(R.string.hint_try_clicking_the_activate_button_to_activate_the_plugin)
).joinToString("\n\n")
MaterialDialog.Builder(contextRef)
.title(R.string.error_failed_to_enable_the_plugin)
.content(message)
.neutralText(R.string.dialog_button_copy)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { d, _ ->
ClipboardUtils.setClip(contextRef, errorBody)
ViewUtils.showSnack(d.view, R.string.text_already_copied_to_clip, false)
}
.negativeText(R.string.dialog_button_dismiss)
.negativeColorRes(R.color.dialog_button_default)
.onNegative { d, _ -> d.dismiss() }
.apply positive@{
positiveText(R.string.dialog_button_activate)
val openIntent = wakeIntent ?: launchIntent ?: run {
positiveColorRes(R.color.dialog_button_unavailable)
onPositive { d, _ ->
ViewUtils.showSnack(d.view, R.string.text_unavailable, false)
}
return@positive
}
positiveColorRes(R.color.dialog_button_attraction)
onPositive { d, _ ->
val started = openIntent.startSafely(contextRef, true)
d.dismiss()
if (started) {
ViewUtils.showToast(contextRef, getString(R.string.text_activated_successfully), true)
tryEnableAfterWake(item)
}
}
}
.cancelable(false)
.autoDismiss(false)
.show()
}
vm.setEnabled(contextRef, item.packageName, false)
item.isEnabled = false
adapter.notifyDataSetChanged()
}
}
}
override fun onUninstall(item: PluginCenterItem) {
@@ -292,6 +364,29 @@ class PluginCenterFragment : Fragment(R.layout.fragment_plugin_center) {
}
}
private fun tryEnableAfterWake(item: PluginCenterItem) {
viewLifecycleOwner.lifecycleScope.launch {
val delays = longArrayOf(300L, 800L, 1500L)
for (delayMs in delays) {
delay(delayMs)
val error = runCatching {
PaddleOcrPluginHost.probe(contextRef, item.packageName)
}.exceptionOrNull()
if (error == null) {
if (isAdded && _binding != null) {
vm.setEnabled(contextRef, item.packageName, true)
item.isEnabled = true
adapter.notifyDataSetChanged()
}
return@launch
}
if (error is CancellationException) {
return@launch
}
}
}
}
override fun onStart() {
super.onStart()
pkgReceiver ?: run registerPackageReceiver@{
@@ -324,6 +419,7 @@ class PluginCenterFragment : Fragment(R.layout.fragment_plugin_center) {
// Package uninstallation (pre-update phase) does not record "Recently uninstalled" when replacing.
// zh-CN: 替换卸载 (更新前阶段) 不记录 "最近卸载".
if (!replacing) {
PluginWakeManager.clearAutoWakeAttempt(packageName)
PluginRecentStore.setLastUninstalled(packageName)
vm.load(context, forceRefreshIndex = false)
return

View File

@@ -32,6 +32,7 @@ import org.autojs.autojs6.R
*
* Created by JetBrains AI Assistant (GPT-5.2) on Nov 26, 2025.
* Modified by SuperMonster003 as of Jan 17, 2026.
* Modified by JetBrains AI Assistant (GPT-5.2-Codex (xhigh)) as of Feb 13, 2026.
*/
class PluginCenterViewModel : ViewModel() {
@@ -92,6 +93,15 @@ class PluginCenterViewModel : ViewModel() {
return@launch
}
installed.forEach { local ->
if (local.bindError != null) {
enableStore.setEnabled(context, local.packageName, false)
}
if (local.isStopped) {
PluginWakeManager.tryAutoWakeIfNeeded(context, local.packageName)
}
}
// Render list using "local only".
// zh-CN: 使用 "仅本地" 渲染列表.
val onlyLocalItems = installed.mapNotNull { local ->
@@ -217,4 +227,4 @@ class PluginCenterViewModel : ViewModel() {
private const val TAG = "PluginCenterViewModel"
}
}
}

View File

@@ -0,0 +1,65 @@
package org.autojs.autojs.core.plugin.center
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.content.edit
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.util.IntentUtils.startSafely
/**
* Created by JetBrains AI Assistant (GPT-5.2-Codex (xhigh)) on Feb 13, 2026.
*/
object PluginWakeManager {
private const val SP = "plugin_center_wake"
private const val ACTION_OCR_WAKE = "org.autojs.plugin.PADDLE_OCR.WAKE"
private const val WAKE_ACTIVITY_META = "org.autojs.plugin.WAKE_ACTIVITY"
private val storeContext by lazy { GlobalAppContext.get() }
fun tryAutoWakeIfNeeded(context: Context, packageName: String): Boolean {
if (isAutoWakeAttempted(packageName)) return false
val intent = buildWakeIntent(context, packageName) ?: return false
val started = intent.startSafely(context, true)
markAutoWakeAttempted(packageName)
return started
}
fun buildWakeIntent(context: Context, packageName: String): Intent? {
val pm = context.packageManager
val appInfo = runCatching { pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA) }.getOrNull()
val metaWakeActivity = appInfo?.metaData?.getString(WAKE_ACTIVITY_META)
val metaComponent = metaWakeActivity?.let { className ->
val fullName = if (className.startsWith(".")) packageName + className else className
ComponentName(packageName, fullName)
}
val wakeComponent = metaComponent ?: run {
val implicitIntent = Intent(ACTION_OCR_WAKE).setPackage(packageName)
val info = pm.queryIntentActivities(implicitIntent, 0).firstOrNull()
info?.activityInfo?.let { ComponentName(it.packageName, it.name) }
}
return when {
wakeComponent != null -> Intent().setComponent(wakeComponent)
else -> Intent(ACTION_OCR_WAKE).setPackage(packageName).takeIf { it.resolveActivity(pm) != null }
}
}
private fun isAutoWakeAttempted(packageName: String): Boolean {
val sp = storeContext.getSharedPreferences(SP, Context.MODE_PRIVATE)
return sp.getBoolean(key(packageName), false)
}
private fun markAutoWakeAttempted(packageName: String) {
val sp = storeContext.getSharedPreferences(SP, Context.MODE_PRIVATE)
sp.edit { putBoolean(key(packageName), true) }
}
fun clearAutoWakeAttempt(packageName: String) {
val sp = storeContext.getSharedPreferences(SP, Context.MODE_PRIVATE)
sp.edit { remove(key(packageName)) }
}
private fun key(packageName: String) = "key_\$_auto_wake_attempted_\$_$packageName"
}

View File

@@ -4,6 +4,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.graphics.Bitmap
@@ -28,6 +29,9 @@ import org.autojs.plugin.paddle.ocr.api.PluginInfo
import java.io.File
import java.io.FileOutputStream
/**
* Modified by JetBrains AI Assistant (GPT-5.2-Codex (xhigh)) as of Feb 13, 2026.
*/
object PaddleOcrPluginHost {
private const val TAG = "PaddleOcrPluginHost"
@@ -37,29 +41,36 @@ object PaddleOcrPluginHost {
private const val DEFAULT_BIND_TIMEOUT_MS = 60_000L
private const val DEFAULT_CALL_TIMEOUT_MS = 60_000L
private val externalServiceFlag = runCatching { ServiceInfo::class.java.getField("FLAG_EXTERNAL_SERVICE").getInt(null) }.getOrNull() ?: 0
private val bindExternalServiceFlag = runCatching { Context::class.java.getField("BIND_EXTERNAL_SERVICE").getInt(null) }.getOrNull() ?: 0
data class Discovered(
val serviceInfo: ServiceInfo,
val pluginInfo: PluginInfo?,
val error: Throwable?,
)
suspend fun discover(context: Context): List<Discovered> {
val pm = context.packageManager
val resolveList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.queryIntentServices(Intent(ACTION_OCR), PackageManager.ResolveInfoFlags.of(0))
} else {
pm.queryIntentServices(Intent(ACTION_OCR), 0)
}
val services = resolveList.mapNotNull { it.serviceInfo }
val services = queryOcrServices(context)
Log.i(TAG, "discover: services=${services.size}")
return services.map { svc ->
Log.i(TAG, "discover: ${svc.packageName}/${svc.name}")
val info = runCatching { withService(context, svc, DEFAULT_BIND_TIMEOUT_MS) { it.getInfo() } }
.onFailure { e -> Log.w(TAG, "getInfo failed: ${e.message}") }
.getOrNull()
Discovered(svc, info)
val result = runCatching { withService(context, svc, DEFAULT_BIND_TIMEOUT_MS) { it.getInfo() } }
val info = result.getOrNull()
val error = result.exceptionOrNull()
if (error != null) {
Log.w(TAG, "getInfo failed: ${error.message}")
}
Discovered(svc, info, error)
}
}
suspend fun probe(context: Context, packageName: String): PluginInfo {
val serviceInfo = queryOcrServices(context, packageName).firstOrNull()
?: error("No OCR service found for package: $packageName")
return withService(context, serviceInfo, DEFAULT_BIND_TIMEOUT_MS) { it.getInfo() }
}
suspend fun recognizeText(
context: Context,
target: Discovered,
@@ -141,6 +152,40 @@ object PaddleOcrPluginHost {
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY)
}
private fun queryOcrServices(context: Context, packageName: String? = null): List<ServiceInfo> {
val pm = context.packageManager
val intent = Intent(ACTION_OCR).apply {
if (!packageName.isNullOrBlank()) {
setPackage(packageName)
}
}
val resolveList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.queryIntentServices(intent, PackageManager.ResolveInfoFlags.of(0))
} else {
pm.queryIntentServices(intent, 0)
}
return resolveList.mapNotNull { it.serviceInfo }
}
private fun buildBindFlags(serviceInfo: ServiceInfo): Int {
var flags = Context.BIND_AUTO_CREATE
if (externalServiceFlag != 0 && bindExternalServiceFlag != 0 && (serviceInfo.flags and externalServiceFlag) != 0) {
flags = flags or bindExternalServiceFlag
}
return flags
}
private fun buildBindFailureMessage(context: Context, serviceInfo: ServiceInfo, cn: ComponentName, reason: String? = null): String {
val pm = context.packageManager
val perm = serviceInfo.permission
val hasPerm = perm.isNullOrBlank() || pm.checkPermission(perm, context.packageName) == PackageManager.PERMISSION_GRANTED
val appInfo = serviceInfo.applicationInfo
val appStopped = (appInfo.flags and ApplicationInfo.FLAG_STOPPED) != 0
val isExternal = externalServiceFlag != 0 && (serviceInfo.flags and externalServiceFlag) != 0
val reasonText = reason?.let { " | reason=$it" } ?: ""
return "bindService failed: $cn. exported=${serviceInfo.exported} enabled=${serviceInfo.enabled} appEnabled=${appInfo.enabled} stopped=$appStopped perm=$perm hasPerm=$hasPerm external=$isExternal flags=0x${Integer.toHexString(serviceInfo.flags)}$reasonText"
}
private suspend fun <T> withService(
context: Context,
serviceInfo: ServiceInfo,
@@ -180,9 +225,10 @@ object PaddleOcrPluginHost {
}
}
val ok = try {
val bindFlags = buildBindFlags(serviceInfo)
var ok = try {
Log.i(TAG, "bindService: $cn")
appCtx.bindService(intent, conn, Context.BIND_AUTO_CREATE)
appCtx.bindService(intent, conn, bindFlags)
} catch (se: SecurityException) {
Log.e(TAG, "bindService SecurityException: $cn | ${se.message}")
cont.resumeWith(
@@ -194,9 +240,25 @@ object PaddleOcrPluginHost {
)
return@suspendCancellableCoroutine
}
if (!ok && bindFlags != Context.BIND_AUTO_CREATE) {
ok = try {
appCtx.bindService(intent, conn, Context.BIND_AUTO_CREATE)
} catch (se: SecurityException) {
Log.e(TAG, "bindService SecurityException: $cn | ${se.message}")
cont.resumeWith(
Result.failure(
IllegalStateException(
"bindService SecurityException: $cn. Please make sure the plugin declares <uses-permission android:name=\"org.autojs.permission.PLUGIN\"/> and the Service uses this permission.", se
)
)
)
return@suspendCancellableCoroutine
}
}
if (!ok) {
Log.e(TAG, "bindService failed: $cn")
cont.resumeWith(Result.failure(IllegalStateException("bindService failed: $cn")))
val msg = buildBindFailureMessage(appCtx, serviceInfo, cn)
Log.e(TAG, msg)
cont.resumeWith(Result.failure(IllegalStateException(msg)))
return@suspendCancellableCoroutine
}

View File

@@ -1333,4 +1333,11 @@
<string name="warn_exit_without_saving_settings">لم يتم حفظ الاعدادات. هل تريد الخروج?</string>
<string name="error_failed_to_save">فشل الحفظ</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">جارٍ الحفظ...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">فشل في تفعيل الاضافة</string>
<string name="text_exception_info">معلومات الاستثناء</string>
<string name="text_hint">تلميح</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">جرّب الضغط على زر \"تفعيل\" لتفعيل الاضافة مرة واحدة.\nاذا نجح التفعيل، فسيتم تشغيل الزر تلقائيا خلال فترة زمنية معينة.</string>
<string name="dialog_button_activate">تفعيل</string>
<string name="text_activated_successfully">تم التفعيل بنجاح</string>
</resources>

View File

@@ -1328,4 +1328,11 @@
<string name="warn_exit_without_saving_settings">The settings has not been saved, are you sure to exit?</string>
<string name="error_failed_to_save">Failed to save</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">Saving...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">Failed to enable the plugin</string>
<string name="text_exception_info">Exception info</string>
<string name="text_hint">Hint</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">Try clicking the \"Activate\" button to activate the plugin once.\nIf activation is successful, the button will automatically turn on within a certain period of time.</string>
<string name="dialog_button_activate">Activate</string>
<string name="text_activated_successfully">Activated successfully</string>
</resources>

View File

@@ -1331,4 +1331,11 @@
<string name="warn_exit_without_saving_settings">La configuracion no se ha guardado. ¿Seguro que quieres salir?</string>
<string name="error_failed_to_save">Error al guardar</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">Guardando...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">No se pudo activar el plugin</string>
<string name="text_exception_info">Info de la excepcion</string>
<string name="text_hint">Sugerencia</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">Prueba a pulsar el boton \"Activar\" para activar el plugin una vez.\nSi la activacion se realiza correctamente, el boton se activara automaticamente en un periodo de tiempo.</string>
<string name="dialog_button_activate">Activar</string>
<string name="text_activated_successfully">Activado correctamente</string>
</resources>

View File

@@ -1331,4 +1331,11 @@
<string name="warn_exit_without_saving_settings">Les parametres ne sont pas enregistres. Quitter?</string>
<string name="error_failed_to_save">Echec de l\'enregistrement</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">Enregistrement...</string>
<string name="symbol_colon_with_blank">" : "</string>
<string name="error_failed_to_enable_the_plugin">Echec de l\'activation du plugin</string>
<string name="text_exception_info">Infos de l\'exception</string>
<string name="text_hint">Astuce</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">Essayez d\'appuyer sur le bouton \"Activer\" pour activer le plugin une fois.\nSi l\'activation reussit, le bouton s\'activera automatiquement dans un certain delai.</string>
<string name="dialog_button_activate">Activer</string>
<string name="text_activated_successfully">Activation reussie</string>
</resources>

View File

@@ -1332,4 +1332,11 @@
<string name="warn_exit_without_saving_settings">設定が保存されていません. 終了しますか?</string>
<string name="error_failed_to_save">保存に失敗しました</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">保存中...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">プラグインの有効化に失敗しました</string>
<string name="text_exception_info">例外情報</string>
<string name="text_hint">ヒント</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">\"有効化\" ボタンをタップして, プラグインを一度有効化してみてください.\n有効化に成功すると, 一定時間内にボタンが自動的にオンになります.</string>
<string name="dialog_button_activate">有効化</string>
<string name="text_activated_successfully">有効化しました</string>
</resources>

View File

@@ -1333,4 +1333,11 @@
<string name="warn_exit_without_saving_settings">설정이 저장되지 않았습니다. 종료할까요?</string>
<string name="error_failed_to_save">저장에 실패했습니다</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">저장 중...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">플러그인을 활성화하지 못했습니다</string>
<string name="text_exception_info">예외 정보</string>
<string name="text_hint">힌트</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">\"활성화\" 버튼을 눌러 플러그인을 한 번 활성화해 보세요.\n활성화에 성공하면 일정 시간 내에 버튼이 자동으로 켜집니다.</string>
<string name="dialog_button_activate">활성화</string>
<string name="text_activated_successfully">활성화되었습니다</string>
</resources>

View File

@@ -1331,4 +1331,11 @@
<string name="warn_exit_without_saving_settings">Настройки не сохранены. Выйти?</string>
<string name="error_failed_to_save">Не удалось сохранить</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">Сохранение...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">Не удалось включить плагин</string>
<string name="text_exception_info">Сведения об исключении</string>
<string name="text_hint">Подсказка</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">Попробуйте нажать кнопку \"Активировать\", чтобы активировать плагин один раз.\nЕсли активация пройдет успешно, кнопка автоматически включится в течение некоторого времени.</string>
<string name="dialog_button_activate">Активировать</string>
<string name="text_activated_successfully">Активация выполнена</string>
</resources>

View File

@@ -1327,4 +1327,11 @@
<string name="warn_exit_without_saving_settings">設置尚未保存, 確定要退出嗎</string>
<string name="error_failed_to_save">保存失敗</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">正在保存...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">插件啓用失敗</string>
<string name="text_exception_info">異常信息</string>
<string name="text_hint">提示</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">可嘗試點擊 \"激活\" 按鈕激活一次插件.\n如激活成功, 按鈕將在一定時間內自動開啓.</string>
<string name="dialog_button_activate">激活</string>
<string name="text_activated_successfully">激活成功</string>
</resources>

View File

@@ -1327,4 +1327,11 @@
<string name="warn_exit_without_saving_settings">設定尚未儲存, 確定要退出嗎</string>
<string name="error_failed_to_save">儲存失敗</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">正在儲存...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">外掛啟用失敗</string>
<string name="text_exception_info">異常資訊</string>
<string name="text_hint">提示</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">可嘗試點選 \"啟用\" 按鈕啟用一次外掛.\n如啟用成功, 按鈕將在一定時間內自動開啟.</string>
<string name="dialog_button_activate">啟用</string>
<string name="text_activated_successfully">啟用成功</string>
</resources>

View File

@@ -1328,4 +1328,11 @@
<string name="warn_exit_without_saving_settings">设置尚未保存, 确定要退出吗</string>
<string name="error_failed_to_save">保存失败</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">正在保存...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">插件启用失败</string>
<string name="text_exception_info">异常信息</string>
<string name="text_hint">提示</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">可尝试点击 \"激活\" 按钮激活一次插件.\n如激活成功, 按钮将在一定时间内自动开启.</string>
<string name="dialog_button_activate">激活</string>
<string name="text_activated_successfully">激活成功</string>
</resources>

View File

@@ -1602,4 +1602,11 @@
<string name="warn_exit_without_saving_settings">The settings has not been saved, are you sure to exit?</string>
<string name="error_failed_to_save">Failed to save</string>
<string name="text_saving" tools:ignore="TypographyEllipsis">Saving...</string>
<string name="symbol_colon_with_blank">": "</string>
<string name="error_failed_to_enable_the_plugin">Failed to enable the plugin</string>
<string name="text_exception_info">Exception info</string>
<string name="text_hint">Hint</string>
<string name="hint_try_clicking_the_activate_button_to_activate_the_plugin">Try clicking the \"Activate\" button to activate the plugin once.\nIf activation is successful, the button will automatically turn on within a certain period of time.</string>
<string name="dialog_button_activate">Activate</string>
<string name="text_activated_successfully">Activated successfully</string>
</resources>