diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index f1a487d3..4b23d875 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -1,7 +1,7 @@ { "$data": { "v6.6.3": { - "released_date": "2025/04/19", + "released_date": "2025/04/26", "feature": [ "timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态", "engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件" @@ -17,9 +17,11 @@ ], "improvement": [ "打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_", + "版本更新信息支持多语言显示 (与当前显示语言同步)", "使用异步加载方式一定程度提升文件管理器列表滑动流畅性" ], "dependency": [ + "附加 Jsoup 版本 1.19.1", "升级 Gradle 版本 8.14-rc-1 -> 8.14-rc-2" ] }, diff --git a/.python/generate_markdown.py b/.python/generate_markdown.py index 3fdcd699..01e38d31 100644 --- a/.python/generate_markdown.py +++ b/.python/generate_markdown.py @@ -159,18 +159,19 @@ def extract_latest_versions(lang_code, lang_content, num_versions=3): dependency_text = lang_content['text_changelog_item_dependency'] # noinspection HttpUrlsUsage base_url = 'http://project.autojs6.com/blob/master/app/src/main/assets-app/doc/CHANGELOG.md' - formatted_version = ( - f'{version.strip()}\n' - f'* `{improvement_label}` {dependency_text} ' - f'_[`CHANGELOG.md`]({base_url}#{version_url_fragment})_\n' - ) - # 去除以 `依赖` label 开头的行 - filtered_version = "\n".join( - line for line in formatted_version.strip().split("\n") + original_lines = version.strip().split("\n") + filtered_content = "\n".join( + line for line in original_lines if not line.strip().startswith(f"* `{changelog_content['changelog_label_dependency']}`") ) - filtered_versions.append(filtered_version + '\n') + is_filtered = len(original_lines) != len(filtered_content.split("\n")) + + if is_filtered: + filtered_versions.append(filtered_content) + filtered_versions.append(f'* `{improvement_label}` {dependency_text} _[`CHANGELOG.md`]({base_url}#{version_url_fragment})_\n') + else: + filtered_versions.append(f'{filtered_content}\n') return filtered_versions diff --git a/.readme/README-ar.md b/.readme/README-ar.md index 18cf4d12..418109e5 100644 --- a/.readme/README-ar.md +++ b/.readme/README-ar.md @@ -389,7 +389,7 @@ SDK Tools (أدوات SDK) (في النافذة اليمنى) يعتمد مشروع AutoJs6 على إصدار `JDK (مجموعة تطوير جافا)` بإصدار لا يقل عن `17`، ولكن يفضل الإصدار الذي لا يقل عن `19`. -اعتبارًا من 19 April 2025، الإصدار الأقصى المدعوم من JDK لمشروع AutoJs6 هو `24`. +اعتبارًا من 24 April 2025، الإصدار الأقصى المدعوم من JDK لمشروع AutoJs6 هو `24`. > ملاحظة: إذا كان نظام الكمبيوتر يحتوي على JDK والإصدار يفي بالمتطلبات المذكورة أعلاه، فيمكنك تخطي هذا القسم. diff --git a/.readme/README-en.md b/.readme/README-en.md index 9f6a48ab..06658ec1 100644 --- a/.readme/README-en.md +++ b/.readme/README-en.md @@ -389,7 +389,7 @@ Check `Show Package Details`, click NDK and CMake respectively to ensure the cor The `JDK (Java Development Kit)` version required for the AutoJs6 project should be at least `17`, but `19` or higher is recommended. -As of Apr 19, 2025, AutoJs6 supports up to version `24` of the JDK. +As of Apr 24, 2025, AutoJs6 supports up to version `24` of the JDK. > Note: If the JDK is already installed on the computer system and the version meets the above requirements, this section can be skipped. diff --git a/.readme/README-es.md b/.readme/README-es.md index 85f6c260..0064e9f1 100644 --- a/.readme/README-es.md +++ b/.readme/README-es.md @@ -389,7 +389,7 @@ Marca `Show Package Details (Mostrar detalles del paquete)`, haz clic en NDK y C La versión mínima requerida del `JDK (Kit de Desarrollo de Java)` para el proyecto AutoJs6 es `17`, pero se recomienda usar una versión no inferior a `19`. -Hasta el 19 de April de 2025, AutoJs6 soporta hasta la versión `24` de JDK. +Hasta el 24 de April de 2025, AutoJs6 soporta hasta la versión `24` de JDK. > Nota: Si el sistema ya tiene instalado JDK y cumple con los requisitos anteriores, puedes omitir esta sección. diff --git a/.readme/README-fr.md b/.readme/README-fr.md index 62087e36..36586d0b 100644 --- a/.readme/README-fr.md +++ b/.readme/README-fr.md @@ -389,7 +389,7 @@ Cochez `Show Package Details (afficher les détails du package)`, puis cliquez r La version de JDK (Kit de développement Java) requise pour le projet AutoJs6 doit être au moins `17`, mais il est recommandé d'avoir au moins `19`. -À partir du 19 April 2025, AutoJs6 prend en charge la version maximale de JDK `24`. +À partir du 24 April 2025, AutoJs6 prend en charge la version maximale de JDK `24`. > Note : Si le JDK est déjà installé sur le système informatique et que la version répond aux exigences ci-dessus, vous pouvez ignorer cette section. diff --git a/.readme/README-ja.md b/.readme/README-ja.md index addea4b7..4a5bf742 100644 --- a/.readme/README-ja.md +++ b/.readme/README-ja.md @@ -389,7 +389,7 @@ SDK Tools (SDK ツール) (右側のウィンドウにあります) AutoJs6プロジェクトが依存する `JDK (Java開発キット)` のリリースバージョンは少なくとも `17` で, それ以下のバージョンは推奨されませんが, 最低でも `19` の使用を推奨します. -2025 年 4 月 19 日 現在, AutoJs6がサポートする最大のJDKバージョンは `24` です. +2025 年 4 月 24 日 現在, AutoJs6がサポートする最大のJDKバージョンは `24` です. > 注: コンピュータシステムに適切なバージョンのJDKがインストールされている場合, この節の内容をスキップできます. diff --git a/.readme/README-ko.md b/.readme/README-ko.md index 016e7bf0..eeb95466 100644 --- a/.readme/README-ko.md +++ b/.readme/README-ko.md @@ -389,7 +389,7 @@ SDK Tools (SDK 도구) (오른쪽 창에 위치) AutoJs6 프로젝트는 `JDK (Java 개발 도구 키트)` 버전이 `17` 이상이어야 하지만, `19` 이상을 권장합니다. -2025 년 4 월 19 일 기준으로, AutoJs6이 지원하는 최대 JDK 버전은 `24`입니다. +2025 년 4 월 24 일 기준으로, AutoJs6이 지원하는 최대 JDK 버전은 `24`입니다. > 메모: 시스템에 JDK가 설치되어 있고, 버전이 위의 요구 사항을 충족하는 경우, 이 섹션을 건너뛸 수 있습니다. diff --git a/.readme/README-ru.md b/.readme/README-ru.md index e770b082..5f3d5c70 100644 --- a/.readme/README-ru.md +++ b/.readme/README-ru.md @@ -389,7 +389,7 @@ SDK Tools (SDK Инструменты) (находится в правом ок Для проекта AutoJs6 требуется версия `JDK (Java Development Kit)` не ниже `17`, но рекомендуется не ниже `19`. -По состоянию на 19 April 2025 года AutoJs6 поддерживает JDK максимальной версии `24`. +По состоянию на 24 April 2025 года AutoJs6 поддерживает JDK максимальной версии `24`. > Примечание: Если на компьютере уже установлен JDK и версия соответствует указанным требованиям, этот раздел можно пропустить. diff --git a/.readme/README-zh-Hans.md b/.readme/README-zh-Hans.md index cee74697..91a99a1c 100644 --- a/.readme/README-zh-Hans.md +++ b/.readme/README-zh-Hans.md @@ -157,7 +157,7 @@ AutoJs6 在 Auto.js 最终项目的基础上, 于 `2021/12/01` 进行二次开 # v6.6.3 -###### 2025/04/19 +###### 2025/04/24 * `新增` timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态 * `新增` engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件 @@ -167,6 +167,7 @@ AutoJs6 在 Auto.js 最终项目的基础上, 于 `2021/12/01` 进行二次开 * `修复` APK 文件类型信息对话框可能无法获取应用名称及 SDK 信息的问题 * `修复` dialogs 模块无法正常使用 customView 属性的问题 _[`issue #364`](http://issues.autojs6.com/364)_ * `修复` console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_ +* `修复` README.md 中部分语言日期格式不正确的问题 * `优化` 打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_ * `优化` 使用异步加载方式一定程度提升文件管理器列表滑动流畅性 * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets-app/doc/CHANGELOG.md#v663)_ @@ -315,7 +316,7 @@ SDK Tools (SDK 工具) (位于右侧窗口) AutoJs6 项目依赖的 `JDK (Java 开发工具包)` 发行版本不低于 `17`, 但建议不低于 `19`. -截至 2025 年 4 月 19 日, AutoJs6 可支持 JDK 最高版本为 `24`. +截至 2025 年 4 月 24 日, AutoJs6 可支持 JDK 最高版本为 `24`. > 注: 如果计算机系统已安装 JDK 且版本满足上述要求, 则可跳过此小节内容. diff --git a/.readme/README-zh-Hant-HK.md b/.readme/README-zh-Hant-HK.md index 6ae40fad..6b50d34f 100644 --- a/.readme/README-zh-Hant-HK.md +++ b/.readme/README-zh-Hant-HK.md @@ -389,7 +389,7 @@ SDK Tools (SDK 工具) (位於右側窗口) AutoJs6 項目依賴的 `JDK (Java 開發工具包)` 發行版本不低於 `17`, 但建議不低於 `19`. -截至 2025 年 4 月 19 日, AutoJs6 可支持 JDK 最高版本為 `24`. +截至 2025 年 4 月 24 日, AutoJs6 可支持 JDK 最高版本為 `24`. > 注: 如果計算機系統已安裝 JDK 且版本滿足上述要求, 則可跳過此小節內容. diff --git a/.readme/README-zh-Hant-TW.md b/.readme/README-zh-Hant-TW.md index ead0868a..fd7cf7ee 100644 --- a/.readme/README-zh-Hant-TW.md +++ b/.readme/README-zh-Hant-TW.md @@ -389,7 +389,7 @@ SDK Tools (SDK 工具) (位於右側視窗) AutoJs6 專案依賴的 `JDK (Java 開發工具包)` 發行版本不低於 `17`, 但建議不低於 `19`. -截至 2025 年 4 月 19 日, AutoJs6 可支援 JDK 最高版本為 `24`. +截至 2025 年 4 月 24 日, AutoJs6 可支援 JDK 最高版本為 `24`. > 注: 如果計算機系統已安裝 JDK 且版本滿足上述要求, 則可跳過此小節內容. diff --git a/README.md b/README.md index cee74697..91a99a1c 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ AutoJs6 在 Auto.js 最终项目的基础上, 于 `2021/12/01` 进行二次开 # v6.6.3 -###### 2025/04/19 +###### 2025/04/24 * `新增` timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态 * `新增` engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件 @@ -167,6 +167,7 @@ AutoJs6 在 Auto.js 最终项目的基础上, 于 `2021/12/01` 进行二次开 * `修复` APK 文件类型信息对话框可能无法获取应用名称及 SDK 信息的问题 * `修复` dialogs 模块无法正常使用 customView 属性的问题 _[`issue #364`](http://issues.autojs6.com/364)_ * `修复` console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_ +* `修复` README.md 中部分语言日期格式不正确的问题 * `优化` 打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_ * `优化` 使用异步加载方式一定程度提升文件管理器列表滑动流畅性 * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets-app/doc/CHANGELOG.md#v663)_ @@ -315,7 +316,7 @@ SDK Tools (SDK 工具) (位于右侧窗口) AutoJs6 项目依赖的 `JDK (Java 开发工具包)` 发行版本不低于 `17`, 但建议不低于 `19`. -截至 2025 年 4 月 19 日, AutoJs6 可支持 JDK 最高版本为 `24`. +截至 2025 年 4 月 24 日, AutoJs6 可支持 JDK 最高版本为 `24`. > 注: 如果计算机系统已安装 JDK 且版本满足上述要求, 则可跳过此小节内容. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7f621b89..47510ddf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -225,6 +225,9 @@ dependencies /* Unclassified */ { // MQTT implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0") implementation("org.eclipse.paho:org.eclipse.paho.android.service:1.1.1") + + // Jsoup + implementation("org.jsoup:jsoup:1.19.1") } dependencies /* MIME */ { diff --git a/app/src/main/assets-app/doc/CHANGELOG-zh-Hans.md b/app/src/main/assets-app/doc/CHANGELOG-zh-Hans.md index 8b309bb3..22c261f1 100644 --- a/app/src/main/assets-app/doc/CHANGELOG-zh-Hans.md +++ b/app/src/main/assets-app/doc/CHANGELOG-zh-Hans.md @@ -6,7 +6,7 @@ # v6.6.3 -###### 2025/04/19 +###### 2025/04/24 * `新增` timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态 * `新增` engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件 @@ -16,8 +16,10 @@ * `修复` APK 文件类型信息对话框可能无法获取应用名称及 SDK 信息的问题 * `修复` dialogs 模块无法正常使用 customView 属性的问题 _[`issue #364`](http://issues.autojs6.com/364)_ * `修复` console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_ +* `修复` README.md 中部分语言日期格式不正确的问题 * `优化` 打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_ * `优化` 使用异步加载方式一定程度提升文件管理器列表滑动流畅性 +* `依赖` 附加 Jsoup 版本 1.19.1 * `依赖` 升级 Gradle 版本 8.14-rc-1 -> 8.14-rc-2 # v6.6.2 diff --git a/app/src/main/assets-app/doc/CHANGELOG.md b/app/src/main/assets-app/doc/CHANGELOG.md index 8b309bb3..22c261f1 100644 --- a/app/src/main/assets-app/doc/CHANGELOG.md +++ b/app/src/main/assets-app/doc/CHANGELOG.md @@ -6,7 +6,7 @@ # v6.6.3 -###### 2025/04/19 +###### 2025/04/24 * `新增` timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态 * `新增` engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件 @@ -16,8 +16,10 @@ * `修复` APK 文件类型信息对话框可能无法获取应用名称及 SDK 信息的问题 * `修复` dialogs 模块无法正常使用 customView 属性的问题 _[`issue #364`](http://issues.autojs6.com/364)_ * `修复` console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_ +* `修复` README.md 中部分语言日期格式不正确的问题 * `优化` 打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_ * `优化` 使用异步加载方式一定程度提升文件管理器列表滑动流畅性 +* `依赖` 附加 Jsoup 版本 1.19.1 * `依赖` 升级 Gradle 版本 8.14-rc-1 -> 8.14-rc-2 # v6.6.2 diff --git a/app/src/main/java/org/autojs/autojs/core/pref/Language.kt b/app/src/main/java/org/autojs/autojs/core/pref/Language.kt index 2ff6f8c2..4de3a0c9 100644 --- a/app/src/main/java/org/autojs/autojs/core/pref/Language.kt +++ b/app/src/main/java/org/autojs/autojs/core/pref/Language.kt @@ -7,21 +7,44 @@ import org.autojs.autojs.util.StringUtils.key import org.autojs.autojs6.R import java.util.Locale -enum class Language(val locale: Locale, @KeyRes private val keyRes: Int, private val entryNameRes: Int) { +enum class Language(@JvmField val languageTag: String, @KeyRes private val keyRes: Int, private val entryNameRes: Int) { - ZH_HANS(Locale.forLanguageTag("zh-Hans"), R.string.key_app_language_zh_hans, R.string.entry_app_language_zh_hans), - ZH_HANT_HK(Locale.forLanguageTag("zh-Hant-HK"), R.string.key_app_language_zh_hant_hk, R.string.entry_app_language_zh_hant_hk), - ZH_HANT_TW(Locale.forLanguageTag("zh-Hant-TW"), R.string.key_app_language_zh_hant_tw, R.string.entry_app_language_zh_hant_tw), - EN(Locale.forLanguageTag("en"), R.string.key_app_language_en, R.string.entry_app_language_en), - FR(Locale.forLanguageTag("fr"), R.string.key_app_language_fr, R.string.entry_app_language_fr), - ES(Locale.forLanguageTag("es"), R.string.key_app_language_es, R.string.entry_app_language_es), - JA(Locale.forLanguageTag("ja"), R.string.key_app_language_ja, R.string.entry_app_language_ja), - KO(Locale.forLanguageTag("ko"), R.string.key_app_language_ko, R.string.entry_app_language_ko), - RU(Locale.forLanguageTag("ru"), R.string.key_app_language_ru, R.string.entry_app_language_ru), - AR(Locale.forLanguageTag("ar"), R.string.key_app_language_ar, R.string.entry_app_language_ar), - AUTO(LocaleUtils.getSystemLocale(), R.string.key_app_language_auto, R.string.entry_app_language_auto), + ZH_HANS("zh-Hans", R.string.key_app_language_zh_hans, R.string.entry_app_language_zh_hans), + ZH_HANT_HK("zh-Hant-HK", R.string.key_app_language_zh_hant_hk, R.string.entry_app_language_zh_hant_hk), + ZH_HANT_TW("zh-Hant-TW", R.string.key_app_language_zh_hant_tw, R.string.entry_app_language_zh_hant_tw), + EN("en", R.string.key_app_language_en, R.string.entry_app_language_en), + FR("fr", R.string.key_app_language_fr, R.string.entry_app_language_fr), + ES("es", R.string.key_app_language_es, R.string.entry_app_language_es), + JA("ja", R.string.key_app_language_ja, R.string.entry_app_language_ja), + KO("ko", R.string.key_app_language_ko, R.string.entry_app_language_ko), + RU("ru", R.string.key_app_language_ru, R.string.entry_app_language_ru), + AR("ar", R.string.key_app_language_ar, R.string.entry_app_language_ar), + AUTO("", R.string.key_app_language_auto, R.string.entry_app_language_auto), ; + val locale: Locale = when (languageTag.isBlank()) { + true -> LocaleUtils.getSystemLocale() + else -> Locale.forLanguageTag(languageTag) + } + + fun getLocalCompatibleLanguageTag(): String { + if (!languageTag.isBlank()) { + return languageTag + } + + val availableTags = values().map { it.languageTag } + val systemTag = LocaleUtils.getSystemLocale().toLanguageTag() + + return when { + systemTag in availableTags -> systemTag + systemTag.contains("-") -> { + val primaryLanguage = systemTag.substringBefore("-") + if (primaryLanguage in availableTags) primaryLanguage + else EN.languageTag + } + else -> EN.languageTag + } + } fun getEntryName(context: Context) = context.getString(entryNameRes) fun getKey() = key(keyRes) @@ -34,12 +57,12 @@ enum class Language(val locale: Locale, @KeyRes private val keyRes: Int, private fun getPrefLanguage() = getPrefLanguageOrNull() ?: AUTO @JvmStatic - fun getPrefLanguageOrNull() = Language.values().find { + fun getPrefLanguageOrNull() = values().find { it.getKey() == Pref.getStringOrNull(R.string.key_app_language) } fun setPrefLanguage(locale: Locale) { - val candidates = Language.values().filter { it != AUTO } + val candidates = values().filter { it != AUTO } val splitLocale = locale.toLanguageTag().split("-") var maxLettersLen = 3 do { diff --git a/app/src/main/java/org/autojs/autojs/extension/ScriptableExtensions.kt b/app/src/main/java/org/autojs/autojs/extension/ScriptableExtensions.kt index 87cdfd10..728f2cdf 100644 --- a/app/src/main/java/org/autojs/autojs/extension/ScriptableExtensions.kt +++ b/app/src/main/java/org/autojs/autojs/extension/ScriptableExtensions.kt @@ -3,6 +3,7 @@ package org.autojs.autojs.extension import org.autojs.autojs.util.RhinoUtils.undefined import org.mozilla.javascript.Scriptable import org.mozilla.javascript.ScriptableObject +import org.mozilla.javascript.Wrapper /** * Created by SuperMonster003 on Sep 28, 2024. @@ -17,7 +18,9 @@ object ScriptableExtensions { * @param key A property name * @return The value of a property with name `key` found in this object or any object in its prototype chain, or [Scriptable.NOT_FOUND] if not found */ - fun Scriptable.getProp(key: String): Any? = ScriptableObject.getProperty(this, key) + fun Scriptable.getProp(key: String): Any? = ScriptableObject.getProperty(this, key).let { + if (it is Wrapper) it.unwrap() else it + } /** * This method is a clone of [Scriptable.getProp]. diff --git a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java index 36d96db1..6318b65f 100644 --- a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java +++ b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java @@ -8,30 +8,39 @@ import android.text.Html; import android.text.Spanned; import android.util.Log; import android.view.View; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.internal.MDButton; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.gson.GsonBuilder; - +import com.google.gson.Strictness; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Consumer; +import io.reactivex.schedulers.Schedulers; +import okhttp3.ResponseBody; import org.autojs.autojs.annotation.Lazy; +import org.autojs.autojs.core.pref.Language; +import org.autojs.autojs.core.pref.Pref; import org.autojs.autojs.core.ui.widget.CustomSnackbar; -import org.autojs.autojs.network.api.UpdateCheckerApi; +import org.autojs.autojs.network.api.StreamingUrlApi; import org.autojs.autojs.network.download.DownloadManager; import org.autojs.autojs.network.entity.ExtendedVersionInfo; import org.autojs.autojs.network.entity.VersionInfo; -import org.autojs.autojs.core.pref.Pref; import org.autojs.autojs.tool.SimpleObserver; import org.autojs.autojs.util.AndroidUtils; import org.autojs.autojs.util.IntentUtils; +import org.autojs.autojs.util.TextUtils; import org.autojs.autojs.util.UpdateUtils; import org.autojs.autojs.util.ViewUtils; import org.autojs.autojs6.R; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.kohsuke.github.GHAsset; import org.kohsuke.github.GHRelease; import org.kohsuke.github.GHRepository; @@ -39,70 +48,96 @@ import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; import org.kohsuke.github.MarkdownMode; import org.kohsuke.github.PagedIterable; - -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.functions.Consumer; -import io.reactivex.schedulers.Schedulers; -import okhttp3.ResponseBody; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; +import java.io.BufferedReader; +import java.io.File; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + /** * Created by Stardust on Sep 20, 2017. * Modified by SuperMonster003 as of Feb 26, 2022. + * + * @noinspection CallToPrintStackTrace, unused, ResultOfMethodCallIgnored */ +@SuppressLint("CheckResult") public class UpdateChecker { private static final String TAG = UpdateChecker.class.getSimpleName(); + private MaterialDialog mUpdateDialog; private MaterialDialog mPendingDialog; public enum PromptMode {DIALOG, SNACKBAR} + public static final String URL_BASE_GITHUB_RAW = "https://raw.githubusercontent.com/"; + public static final String URL_BASE_GITHUB_HOME = "https://github.com/"; + + public static final String URL_VERSION_PROPS_RAW = URL_BASE_GITHUB_RAW + "SuperMonster003/AutoJs6/master/version.properties"; + public static final String URL_VERSION_PROPS_BLOB = URL_BASE_GITHUB_HOME + "SuperMonster003/AutoJs6/blob/master/version.properties"; + private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Context mContext; private final View mView; - private final String mBaseUrl; - private final String mUrl; private final PromptMode mPromptMode; private final SimpleObserver mCallback; private final Executor mGitHubExecutor = Executors.newSingleThreadExecutor(); - private UpdateCheckerApi checkerApi; - - private UpdateChecker(Context context, View view, String baseUrl, String url, PromptMode promptMode, SimpleObserver callback) { + private UpdateChecker(Context context, View view, PromptMode promptMode, SimpleObserver callback) { mContext = context; mView = view; - mBaseUrl = baseUrl; - mUrl = url; mPromptMode = promptMode; mCallback = callback; } public void checkNow() { + checkNow(false); + } + + public void checkNow(boolean isIgnoreLocalVersions) { mPendingDialog = new Dialog.Builder.Pending(mContext, R.string.text_checking_update).build(); if (mPromptMode == PromptMode.DIALOG) { mPendingDialog.show(); } - getCheckerApi() - .checkForUpdates(mUrl) + Observable obsBlobSafe = getStreamingApi() + .streamingUrl(URL_VERSION_PROPS_BLOB) .subscribeOn(Schedulers.io()) + .onErrorResumeNext(e -> { + Log.d(TAG, "Error from obsBlobSafe while parsing version.properties"); + e.printStackTrace(); + return Observable.empty(); + }); + + Observable obsRawSafe = getStreamingApi() + .streamingUrl(URL_VERSION_PROPS_RAW) + .subscribeOn(Schedulers.io()) + .onErrorResumeNext(e -> { + Log.d(TAG, "Error from obsRawSafe while parsing version.properties"); + e.printStackTrace(); + return Observable.empty(); + }); + + Observable.amb(Arrays.asList(obsBlobSafe, obsRawSafe)) .observeOn(AndroidSchedulers.mainThread()) + .switchIfEmpty(Observable.error(new Exception("To trigger onError"))) .subscribe(new SimpleObserver<>() { @Override - public void onNext(ResponseBody responseBody) { + public void onNext(@NotNull ResponseBody responseBody) { Pref.refreshLastUpdatesCheckedTimestamp(); if (mPendingDialog.isShowing()) { @@ -120,15 +155,15 @@ public class UpdateChecker { switch (mPromptMode) { case DIALOG -> { if (versionInfo == null) { - ViewUtils.showToast(mContext, R.string.error_parse_version_info); + ViewUtils.showToast(mContext, R.string.error_parse_version_info, true); return; } - if (versionInfo.isNewer() && versionInfo.isNotIgnored()) { - mHandler.post(() -> showDialog(versionInfo)); - } else { - ViewUtils.showToast(mContext, R.string.text_is_latest_version); + if (!isIgnoreLocalVersions && !(versionInfo.isNewer() && versionInfo.isNotIgnored())) { + ViewUtils.showToast(mContext, R.string.text_is_latest_version, true); Pref.refreshLastNoNewerUpdatesTimestamp(); + return; } + mHandler.post(() -> showUpdateInfoDialog(versionInfo)); } case SNACKBAR -> { if (versionInfo != null && versionInfo.isNewer() && versionInfo.isNotIgnored()) { @@ -163,51 +198,12 @@ public class UpdateChecker { @Nullable private VersionInfo getVersionInfo(ResponseBody responseBody) { - VersionInfo versionInfoByBlob = getVersionInfoByBlob(responseBody); - if (versionInfoByBlob != null) { - Log.d(TAG, "VersionInfo parsed via blob"); - return versionInfoByBlob; - } - // VersionInfo versionInfoByProperties = getVersionInfoByProperties(responseBody); - // if (versionInfoByProperties != null) { - // Log.d(TAG, "VersionInfo parsed via properties"); - // return versionInfoByProperties; - // } - return null; - } - - @Nullable - private VersionInfo getVersionInfoByProperties(ResponseBody responseBody) { try { - Properties prop = new Properties(); - prop.load(responseBody.byteStream()); - - String versionNameKey = mContext.getString(R.string.property_key_app_version_name); - String versionName = prop.getProperty(versionNameKey); - - String versionCodeKey = mContext.getString(R.string.property_key_app_version_code); - int versionCode = Integer.parseInt(prop.getProperty(versionCodeKey)); - - return new VersionInfo(versionName, versionCode); + return new VersionInfo(responseBody.string()); } catch (Exception e) { e.printStackTrace(); - return null; - } - } - - @Nullable - private static VersionInfo getVersionInfoByBlob(ResponseBody responseBody) { - StringBuilder textBuilder = new StringBuilder(); - try (Reader r = new BufferedReader(new InputStreamReader(responseBody.byteStream()), 1024)) { - int c; - while ((c = r.read()) != -1) { - textBuilder.append((char) c); - } - return new VersionInfo(textBuilder.toString()); - } catch (Exception e) { - e.printStackTrace(); - return null; } + return null; } @SuppressWarnings("ResultOfMethodCallIgnored") @@ -270,17 +266,18 @@ public class UpdateChecker { } @NonNull - @Lazy - private UpdateCheckerApi getCheckerApi() { - if (checkerApi == null) { - checkerApi = new Retrofit.Builder() - .baseUrl(mBaseUrl) - .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build() - .create(UpdateCheckerApi.class); - } - return checkerApi; + private StreamingUrlApi getStreamingApi() { + return getStreamingApi(UpdateChecker.URL_BASE_GITHUB_HOME); + } + + @NonNull + private StreamingUrlApi getStreamingApi(String baseUrl) { + return new Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setStrictness(Strictness.LENIENT).create())) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() + .create(StreamingUrlApi.class); } private void showSnackBar(@NonNull VersionInfo versionInfo) { @@ -289,23 +286,19 @@ public class UpdateChecker { CustomSnackbar.make(mView, content, BaseTransientBottomBar.LENGTH_INDEFINITE) .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) - .setActionOne(R.string.text_updates_snack_bar_act_view, v -> showDialog(versionInfo, mView.getContext())) + .setActionOne(R.string.text_updates_snack_bar_act_view, v -> showUpdateInfoDialog(versionInfo, mView.getContext())) .setActionTwo(R.string.text_updates_snack_bar_act_later, v -> Pref.refreshLastUpdatesPostponedTimestamp()) .show(); } - private void showDialog(@NonNull VersionInfo versionInfo) { - showDialog(versionInfo, mContext); + private void showUpdateInfoDialog(@NonNull VersionInfo versionInfo) { + showUpdateInfoDialog(versionInfo, mContext); } - private void showDialog(@NonNull VersionInfo versionInfo, Context context) { - // TODO by SuperMonster003 on Jun 4, 2022. - // ! VersionInfo: *.properties / *.json. - + private void showUpdateInfoDialog(@NonNull VersionInfo versionInfo, Context context) { String propVersion = versionInfo.getVersionName(); if (propVersion.isEmpty()) { - new Dialog.Builder - .Prompt(context, R.string.error_check_for_update, R.string.error_parse_version_info) + new Dialog.Builder.Prompt(context, R.string.error_check_for_update, R.string.error_parse_version_info) .build().show(); return; } @@ -313,26 +306,7 @@ public class UpdateChecker { mUpdateDialog = new Dialog.Builder.Update(context, propVersion).build(); mPendingDialog = new Dialog.Builder.Pending(context, R.string.text_preparing).build(); - MDButton neutralButton = mUpdateDialog.getActionButton(DialogAction.NEUTRAL); - neutralButton.setOnClickListener(v -> new MaterialDialog.Builder(context) - .title(R.string.text_prompt) - .content(R.string.prompt_add_ignored_version) - .negativeText(R.string.dialog_button_cancel) - .positiveText(R.string.dialog_button_confirm) - .positiveColorRes(R.color.dialog_button_warn) - .onPositive((dPrompt, which) -> { - UpdateUtils.addIgnoredVersion(versionInfo); - ViewUtils.showToast(context, R.string.text_done); - mUpdateDialog.dismiss(); - }) - .show()); - - MDButton negativeButton = mUpdateDialog.getActionButton(DialogAction.NEGATIVE); - negativeButton.setOnClickListener(v -> mUpdateDialog.dismiss()); - - MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE); - positiveButton.setOnClickListener(null); - + setupDialogButtons(versionInfo, context); mUpdateDialog.show(); mGitHubExecutor.execute(() -> { @@ -346,7 +320,7 @@ public class UpdateChecker { String repoName = context.getString(R.string.app_name); GHRepository repo = GHub.getRepo(github, userName, repoName); if (repo == null) { - Dialog.setDialogContent(mUpdateDialog, mUpdateDialog.getContext().getString(R.string.error_invalid_github_repo, repoName)); + Dialog.setDialogContent(mUpdateDialog, context.getString(R.string.error_invalid_github_repo, repoName)); return; } @@ -362,14 +336,7 @@ public class UpdateChecker { return; } - String rawHtmlContent = GHub.getReleaseHtml(repo, release); - Spanned htmlContent = Html.fromHtml(rawHtmlContent, Html.FROM_HTML_MODE_COMPACT); - if (htmlContent.toString().isEmpty()) { - Dialog.setDialogContent(mUpdateDialog, R.string.text_empty_release_note); - } else { - Dialog.setDialogContent(mUpdateDialog, htmlContent); - } - + fetchLatestReleaseNotes(context, versionInfo, repo, release, releaseTag); PagedIterable assets = GHub.getAssets(release); if (assets == null) { mHandler.post(() -> new Dialog.Builder @@ -377,11 +344,253 @@ public class UpdateChecker { .build().show()); return; } - mHandler.post(() -> setDialogUpdateButton(context, assets, versionInfo)); }); } + private void setupDialogButtons(@NonNull VersionInfo versionInfo, Context context) { + MDButton neutralButton = mUpdateDialog.getActionButton(DialogAction.NEUTRAL); + neutralButton.setOnClickListener(v -> new MaterialDialog.Builder(context) + .title(R.string.text_prompt) + .content(R.string.prompt_add_ignored_version) + .negativeText(R.string.dialog_button_cancel) + .positiveText(R.string.dialog_button_confirm) + .positiveColorRes(R.color.dialog_button_warn) + .onPositive((dialog, which) -> { + UpdateUtils.addIgnoredVersion(versionInfo); + ViewUtils.showToast(context, R.string.text_done); + mUpdateDialog.dismiss(); + }) + .show()); + + MDButton negativeButton = mUpdateDialog.getActionButton(DialogAction.NEGATIVE); + negativeButton.setOnClickListener(v -> mUpdateDialog.dismiss()); + + MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE); + positiveButton.setOnClickListener(null); + } + + private void fetchLatestReleaseNotes(Context context, VersionInfo versionInfo, GHRepository repo, GHRelease release, String releaseTag) { + Language language = Objects.requireNonNullElse(Language.getPrefLanguageOrNull(), Language.EN); + String languageTag = language.getLocalCompatibleLanguageTag(); + String urlSuffix = "app/src/main/assets-app/doc/CHANGELOG-" + languageTag + ".md"; + String urlBlob = "https://github.com/SuperMonster003/AutoJs6/blob/master/" + urlSuffix; + String urlRaw = "https://raw.githubusercontent.com/SuperMonster003/AutoJs6/master/" + urlSuffix; + + Observable obsBlob = getStreamingApi() + .streamingUrl(urlBlob) + .subscribeOn(Schedulers.io()) + .onErrorResumeNext(e -> { + Log.d(TAG, "Error from obsBlob while parsing latest release notes from " + urlBlob); + e.printStackTrace(); + return Observable.never(); + }) + .flatMap(responseBody -> { + try { + String content = responseBody.string().trim(); + Log.d(TAG, "Respond body string (first 500) got from github: " + content.substring(0, Math.min(content.length(), 500))); + String html = parseLatestReleaseNotesFromHtml(content); + if (html != null && !html.isBlank()) { + String assembledHtml = assembleBlobDependenciesForSingleVersion(html, versionInfo.getVersionName(), urlBlob); + return Observable.just(Html.fromHtml(assembledHtml, Html.FROM_HTML_MODE_COMPACT)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return Observable.never(); + }); + + Observable obsRaw = getStreamingApi() + .streamingUrl(urlRaw) + .subscribeOn(Schedulers.io()) + .onErrorResumeNext(e -> { + Log.d(TAG, "Error from obsRawSafe while parsing latest release notes from " + urlRaw); + e.printStackTrace(); + return Observable.never(); + }) + .flatMap(responseBody -> { + try { + String content = responseBody.string().trim(); + Log.d(TAG, "Respond body string got from github: " + content); + String markdown = parseLatestReleaseNotesFromMarkdown(content, releaseTag); + if (markdown != null && !markdown.isBlank()) { + String assembledMarkdown = assembleRawDependenciesForSingleVersion(markdown, versionInfo.getVersionName(), urlRaw); + String assembledHtml = TextUtils.renderMarkdown(assembledMarkdown); + return Observable.just(Html.fromHtml(assembledHtml, Html.FROM_HTML_MODE_COMPACT)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return Observable.never(); + }); + + + Observable.amb(Arrays.asList(obsBlob, obsRaw)) + .observeOn(AndroidSchedulers.mainThread()) + .timeout(23, TimeUnit.SECONDS) + .subscribe( + releaseNotesSpannedForSingleVersion -> { + Dialog.setDialogContent(mUpdateDialog, releaseNotesSpannedForSingleVersion); + }, + e -> { + e.printStackTrace(); + Spanned fallbackLatestReleaseNotes = getFallbackLatestReleaseNotes(repo, release); + if (fallbackLatestReleaseNotes == null) { + Dialog.setDialogContent(mUpdateDialog, R.string.error_failed_to_retrieve_released_notes); + return; + } + Dialog.setDialogContent(mUpdateDialog, fallbackLatestReleaseNotes); + if (language != Language.ZH_HANS) { + mHandler.post(() -> new Dialog.Builder + .Prompt(context, R.string.text_prompt, R.string.content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback) + .build().show()); + + } + } + ); + } + + private @NotNull String assembleBlobDependenciesForSingleVersion(@NotNull String releaseNotes, String versionName, String markdownUrl) { + String labelImprovement = mContext.getString(R.string.changelog_label_improvement); + String labelDependency = mContext.getString(R.string.changelog_label_dependency); + + boolean isFiltered = false; + + List filteredReleaseNotes = new ArrayList<>(); + String[] items = releaseNotes.split("\n"); + for (String item : items) { + Log.d(TAG, "item: " + item); + if (item.matches(".*\\b" + labelDependency + "\\b.*")) { + isFiltered = true; + } else { + filteredReleaseNotes.add(item); + } + } + + if (isFiltered) { + String anchor = "v" + String.join("", versionName.split("\\.")); + String dependenciesSummary = mContext.getString(R.string.text_changelog_item_dependency); + + for (int i = filteredReleaseNotes.size() - 1; i >= 0; i--) { + String item = filteredReleaseNotes.get(i); + if (!item.isBlank()) { + String assembledHtml = "
  • " + + labelImprovement + + " " + + dependenciesSummary + + " " + + "CHANGELOG.md" + + "
  • "; + filteredReleaseNotes.add(i + 1, assembledHtml); + break; + } + } + return String.join("\n", filteredReleaseNotes); + } + return releaseNotes; + } + + private @NotNull String assembleRawDependenciesForSingleVersion(@NotNull String releaseNotes, String versionName, String markdownUrl) { + String labelImprovement = mContext.getString(R.string.changelog_label_improvement); + String labelDependency = mContext.getString(R.string.changelog_label_dependency); + + boolean isFiltered = false; + + List filteredReleaseNotes = new ArrayList<>(); + String[] items = releaseNotes.split("\n"); + for (String item : items) { + if (item.contains("`" + labelDependency + "`")) { + isFiltered = true; + } else { + filteredReleaseNotes.add(item); + } + } + + if (isFiltered) { + String anchor = "v" + String.join("", versionName.split("\\.")); + String dependenciesSummary = mContext.getString(R.string.text_changelog_item_dependency); + + for (int i = filteredReleaseNotes.size() - 1; i >= 0; i--) { + String item = filteredReleaseNotes.get(i); + if (!item.isBlank()) { + String assembledMarkdown = "* `" + labelImprovement + "` " + dependenciesSummary + " _[`CHANGELOG.md`](" + markdownUrl + "#" + anchor + ")_"; + filteredReleaseNotes.add(i + 1, assembledMarkdown); + break; + } + } + return String.join("\n", filteredReleaseNotes); + } + return releaseNotes; + } + + private String assembleDependenciesInReleaseNotesListMarkdown(String fullReleaseNotes) { + // TODO by SuperMonster003 on Apr 24. 2025. + // ! Remove all dependency items and append a summary item as improvement. + // ! Reference to `generate_markdown.py`. + return fullReleaseNotes; + } + + @Nullable + private String parseLatestReleaseNotesFromHtml(String htmlContent) { + Document document = Jsoup.parse(htmlContent); + Element releaseDateHeading = document.selectFirst("div.markdown-heading"); + if (releaseDateHeading != null) { + Element logList = null; + Element tmp = releaseDateHeading; + while ((tmp = tmp.nextElementSibling()) != null) { + if ("ul".equals(tmp.tagName())) { + logList = tmp; + break; + } + } + if (logList != null) { + return logList.html(); + } + } + return null; + } + + @Nullable + private String parseLatestReleaseNotesFromMarkdown(String markdown, String releaseTag) { + Pattern pattern = Pattern.compile("#+\\s*" + releaseTag + "([\\s\\S]*?)(?=\\n#+\\s*v\\d+\\.\\d+|\\z)"); + Matcher matcher = pattern.matcher(markdown); + if (matcher.find()) { + String matchedContent = matcher.group(1); + if (matchedContent != null) { + return Pattern.compile("\n").splitAsStream(matchedContent.trim()) + .filter(line -> line.matches("^[*-]\\s*`.*")) + .collect(Collectors.joining("\n")); + } + } + return null; + } + + @Nullable + private Spanned getFallbackLatestReleaseNotes(GHRepository repo, GHRelease release) { + Spanned htmlSpannedContent = getLatestReleaseFromGitHubRelease(repo, release); + if (htmlSpannedContent == null || htmlSpannedContent.toString().isBlank()) { + Log.d(TAG, "Release note got nothing from the latest release (fallback)"); + return null; + } + Log.d(TAG, "Release note got from latest release (fallback)"); + return htmlSpannedContent; + } + + private static @Nullable Spanned getLatestReleaseFromGitHubRelease(GHRepository repo, GHRelease release) { + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + return GHub.getReleaseHtml(repo, release); + }); + try { + String rawHtmlContent = future.get(30, TimeUnit.SECONDS); + return Html.fromHtml(rawHtmlContent, Html.FROM_HTML_MODE_COMPACT); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + private void setDialogUpdateButton(@NonNull Context ctx, PagedIterable ghAssets, VersionInfo versionInfo) { MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE); positiveButton.setTextColor(ctx.getColor(R.color.dialog_button_attraction)); @@ -423,8 +632,6 @@ public class UpdateChecker { private final Context mContext; private View mView; - private String mBaseUrl; - private String mUrl; private PromptMode mPromptMode; private SimpleObserver mCallback; @@ -432,21 +639,11 @@ public class UpdateChecker { mContext = context; } - public Builder(Context context, View view) { - this(context); + public Builder(@NonNull View view) { + this(view.getContext()); mView = view; } - public Builder setBaseUrl(String baseUrl) { - this.mBaseUrl = baseUrl; - return this; - } - - public Builder setUrl(String url) { - this.mUrl = url; - return this; - } - public Builder setPromptMode(PromptMode promptMode) { this.mPromptMode = promptMode; return this; @@ -458,23 +655,7 @@ public class UpdateChecker { } public UpdateChecker build() { - ensureUrl(); - return new UpdateChecker(mContext, mView, mBaseUrl, mUrl, mPromptMode, mCallback); - } - - private void ensureUrl() { - String regexUrl = "^(https?|ftp|file)://[-a-zA-Z\\d+&@#/%?=~_|!:,.;]*[-a-zA-Z\\d+&@#/%=~_|]"; - if (mBaseUrl != null) { - if (!mBaseUrl.matches(regexUrl)) { - System.out.println("Base URL: " + mBaseUrl); - throw new IllegalArgumentException(mContext.getString(R.string.error_illegal_url_argument)); - } - } else { - if (!mUrl.matches(regexUrl)) { - System.out.println("URL: " + mUrl); - throw new IllegalArgumentException(mContext.getString(R.string.error_illegal_relative_url_argument_without_base)); - } - } + return new UpdateChecker(mContext, mView, mPromptMode, mCallback); } } @@ -635,11 +816,11 @@ public class UpdateChecker { while ((c = r.read()) != -1) { textBuilder.append((char) c); } + return textBuilder.toString(); } catch (Exception e) { e.printStackTrace(); return null; } - return textBuilder.toString(); } @Nullable @@ -706,4 +887,4 @@ public class UpdateChecker { } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java b/app/src/main/java/org/autojs/autojs/network/api/StreamingUrlApi.java similarity index 79% rename from app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java rename to app/src/main/java/org/autojs/autojs/network/api/StreamingUrlApi.java index 6b568b37..6d467338 100644 --- a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java +++ b/app/src/main/java/org/autojs/autojs/network/api/StreamingUrlApi.java @@ -11,11 +11,11 @@ import retrofit2.http.Url; * Created by Stardust on Sep 20, 2017. * Modified by SuperMonster003 as of Feb 28, 2022. */ -public interface UpdateCheckerApi { +public interface StreamingUrlApi { @Streaming @GET() @Headers("Cache-Control: no-cache") - Observable checkForUpdates(@Url String url); + Observable streamingUrl(@Url String url); -} +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java index d2bb1038..64ae64e8 100644 --- a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java +++ b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java @@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.internal.MDButton; import org.autojs.autojs.concurrent.VolatileBox; +import org.autojs.autojs.network.UpdateChecker; import org.autojs.autojs.network.api.DownloadApi; import org.autojs.autojs.network.entity.VersionInfo; import org.autojs.autojs.pio.PFiles; @@ -70,7 +71,7 @@ public class DownloadManager { mOkHttpClient = getOkHttpClient(); Retrofit mRetrofit = new Retrofit.Builder() - .baseUrl(UpdateUtils.BASE_URL_RAW) + .baseUrl(UpdateChecker.URL_BASE_GITHUB_RAW) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(mOkHttpClient) .build(); @@ -149,7 +150,7 @@ public class DownloadManager { return download(context, url, path); } - private void initProgressDialog(Context context, String url, @Nullable VersionInfo versionInfo, @Nullable String content) { + private void initProgressDialog(Context context, @NonNull String url, @Nullable VersionInfo versionInfo, @Nullable String content) { mProgressDialog = new MaterialDialog.Builder(context) .title(context.getString(R.string.text_downloading)) .neutralText(R.string.dialog_button_download_with_browser) diff --git a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.kt b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.kt index 48493ee4..06417c13 100644 --- a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.kt +++ b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.kt @@ -37,16 +37,16 @@ class VersionInfo : ExtendedVersionInfo { val regexVersionName = "$versionNameKey(?:)?=([\\w.]+)".toRegex() val regexVersionCode = "$versionCodeKey(?:)?=(\\d+)".toRegex() - for (string in propertiesFileRawString.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { - if (versionName.isEmpty() && string.contains(regexVersionName)) { - versionName = regexVersionName.find(string)?.groupValues?.get(1) ?: "" + for (line in propertiesFileRawString.lineSequence().filter { it.isNotEmpty() }) { + if (versionName.isBlank() && line.contains(regexVersionName)) { + versionName = regexVersionName.find(line)?.groupValues?.get(1).orEmpty() Log.d(TAG, "versionName: $versionName") } - if (versionCode <= 0 && string.contains(regexVersionCode)) { - versionCode = regexVersionCode.find(string)?.groupValues?.get(1)?.toDouble()?.roundToInt() ?: -1 + if (versionCode <= 0 && line.contains(regexVersionCode)) { + versionCode = regexVersionCode.find(line)?.groupValues?.get(1)?.toDouble()?.roundToInt() ?: -1 Log.d(TAG, "versionCode: $versionCode") } - if (versionName.isNotEmpty() && versionCode > 0) { + if (versionName.isNotBlank() && versionCode > 0) { break } } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt index 65a5ff94..0f1b92d4 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt @@ -38,6 +38,7 @@ import java.io.File import java.net.URI import org.autojs.autojs.util.App as PresetApp import androidx.core.net.toUri +import org.autojs.autojs.extension.ScriptableExtensions.deleteProp @Suppress("unused", "UNUSED_PARAMETER") class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @@ -50,13 +51,13 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @Suppress("DEPRECATION") override val selfAssignmentFunctions = listOf( ::intent.name, - ::startActivity.name, - ::startDualActivity.name, + ::startActivity.name to AS_GLOBAL, + ::startDualActivity.name to AS_GLOBAL, ::intentToShell.name, - ::startService.name, - ::sendEmail.name, - ::sendBroadcast.name, - ::sendLocalBroadcastSync.name, + ::startService.name to AS_GLOBAL, + ::sendEmail.name to AS_GLOBAL, + ::sendBroadcast.name to AS_GLOBAL, + ::sendLocalBroadcastSync.name to AS_GLOBAL, ::parseUri.name, ::openUrl.name, ::openDualUrl.name, @@ -74,8 +75,8 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { ::launchDualSettings.name to AS_GLOBAL, ::openAppSetting.name to AS_GLOBAL, ::openDualAppSetting.name to AS_GLOBAL, - ::uninstall.name, - ::uninstallDual.name, + ::uninstall.name to AS_GLOBAL, + ::uninstallDual.name to AS_GLOBAL, ::viewFile.name, ::editFile.name, ::kill.name to AS_GLOBAL, @@ -927,27 +928,45 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { private fun Intent.configure(scriptRuntime: ScriptRuntime, o: NativeObject): Intent { val intent = this - if (!o.prop("url").isJsNullish()) { - o.put("data", o, parseIntentUrl(scriptRuntime, o)) + val urlRaw = o.prop("url") + val packageRaw = o.prop("package") + var packageNameRaw = o.prop("packageName") + val classNameRaw = o.prop("className") + val extrasRaw = o.prop("extras") + val categoryRaw = o.prop("category") + val actionRaw = o.prop("action") + val flagsRaw = o.prop("flags") + val typeRaw = o.prop("type") + val dataRaw = o.prop("data") + + if (!urlRaw.isJsNullish()) { + o.defineProp("data", parseIntentUrl(scriptRuntime, o)) } - if (!o.prop("package").isJsNullish()) { - if (o.prop("packageName").isJsNullish()) { - o.put("packageName", o, o.prop("package")) - } - } else { - if (!o.prop("packageName").isJsNullish()) { - o.put("package", o, o.prop("packageName")) + if (!packageRaw.isJsNullish()) { + if (packageNameRaw.isJsNullish()) { + o.defineProp("packageName", packageRaw).also { + packageNameRaw = packageRaw + } } } + o.deleteProp("package") - if (!o.prop("packageName").isJsNullish()) { - val k = Context.toString(o.prop("packageName")) - if (k in presetPackageNames) { - o.defineProp("packageName", presetPackageNames[k]) + if (!packageNameRaw.isJsNullish()) { + val packageName = when (packageNameRaw) { + is PresetApp -> packageNameRaw.packageName + else -> when (val packageNameStr = coerceString(packageNameRaw)) { + in presetPackageNames -> presetPackageNames[packageNameStr]!! + else -> packageNameStr + } } - if (!o.prop("className").isJsNullish()) { - intent.setClassName(coerceString(o.prop("packageName")), parseClassName(o)) + if (packageName != packageNameRaw) { + o.defineProp("packageName", packageName).also { + packageNameRaw = packageName + } + } + if (!classNameRaw.isJsNullish()) { + intent.setClassName(packageName, parseClassName(o)) } else { // @Hint by SuperMonster003 on Jun 23, 2020. // ! the Intent can only match the components @@ -958,44 +977,43 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { // ! Intent 只能匹配通过 setPackage() 设置的给定应用包中的组件. // ! 否则, 如果有多个应用可以处理该 Intent, // ! 系统会向用户展示一个对话框, 让用户选择要使用哪个应用. - intent.setPackage(coerceString(o.prop("packageName"))) + intent.setPackage(packageName) } } - if (o.prop("extras") is NativeObject) { - (o.prop("extras") as NativeObject).entries.forEach { entry -> + if (extrasRaw is NativeObject) { + extrasRaw.entries.forEach { entry -> val (key, value) = entry RhinoUtils.putExtraForIntent(intent, key, value) } } - if (!o.prop("category").isJsNullish()) { - if (o.prop("category") is Iterable<*>) { - (o.prop("category") as Iterable<*>).forEach { cat -> + if (!categoryRaw.isJsNullish()) { + when (categoryRaw) { + is Iterable<*> -> categoryRaw.forEach { cat -> intent.addCategory(Context.toString(cat)) } - } else { - intent.addCategory(Context.toString(o.prop("category"))) + else -> intent.addCategory(Context.toString(categoryRaw)) } } - if (!o.prop("action").isJsNullish()) { - intent.setAction(parseIntentAction(o.prop("action"))) + if (!actionRaw.isJsNullish()) { + intent.setAction(parseIntentAction(actionRaw)) } - if (!o.prop("flags").isJsNullish()) { - intent.setFlags(parseIntentFlags(o.prop("flags"))) + if (!flagsRaw.isJsNullish()) { + intent.setFlags(parseIntentFlags(flagsRaw)) } - if (!o.prop("type").isJsNullish()) { - if (!o.prop("data").isJsNullish()) { - intent.setDataAndType(parseUriRhinoWithRuntime(scriptRuntime, o.prop("data")), Context.toString(o.prop("type"))) + if (!typeRaw.isJsNullish()) { + if (!dataRaw.isJsNullish()) { + intent.setDataAndType(parseUriRhinoWithRuntime(scriptRuntime, dataRaw), Context.toString(typeRaw)) } else { - intent.setType(Context.toString(o.prop("type"))) + intent.setType(Context.toString(typeRaw)) } } else { - if (!o.prop("data").isJsNullish()) { - intent.setData(Context.toString(o.prop("data")).toUri()) + if (!dataRaw.isJsNullish()) { + intent.setData(Context.toString(dataRaw).toUri()) } } diff --git a/app/src/main/java/org/autojs/autojs/theme/app/ColorSelectBaseActivity.kt b/app/src/main/java/org/autojs/autojs/theme/app/ColorSelectBaseActivity.kt index 9afdcee7..9868434c 100644 --- a/app/src/main/java/org/autojs/autojs/theme/app/ColorSelectBaseActivity.kt +++ b/app/src/main/java/org/autojs/autojs/theme/app/ColorSelectBaseActivity.kt @@ -56,6 +56,7 @@ import org.autojs.autojs6.R import org.autojs.autojs6.R.string.text_search_color import org.greenrobot.eventbus.EventBus import java.util.* +import kotlin.math.absoluteValue import kotlin.math.hypot import kotlin.properties.Delegates @@ -199,7 +200,7 @@ abstract class ColorSelectBaseActivity : BaseActivity() { } protected fun checkAndGetTargetInfoForThemeColorLocate(): TargetInfoForLocate? { - val targetLibraryId = Pref.getInt(KEY_SELECTED_COLOR_LIBRARY_ID, -1) + val targetLibraryId = Pref.getInt(KEY_SELECTED_COLOR_LIBRARY_ID, COLOR_LIBRARY_ID_DEFAULT) if (targetLibraryId == COLOR_LIBRARY_ID_PALETTE) { MaterialDialog.Builder(this) .title(R.string.text_prompt) @@ -214,12 +215,15 @@ abstract class ColorSelectBaseActivity : BaseActivity() { } val targetLibrary = presetColorLibraries.find { it.id == targetLibraryId } if (targetLibrary == null) { + val targetLibraryIdHexString = "0x${targetLibraryId.absoluteValue.toString(16)}".let { + if (targetLibraryId < 0) "-$it" else it + } MaterialDialog.Builder(this) .title(R.string.text_failed_to_locate) .content( getString(R.string.content_failed_to_locate_library_for_theme_color) + "\n" + "\n" + - "Target library ID: 0x${targetLibraryId.toString(16)}" + "\n" + + "Target library ID: $targetLibraryIdHexString" + "\n" + "Color library IDs: [ ${presetColorLibraries.sortedBy { it.id }.joinToString(", ") { "0x${it.id.toString(16)}" }} ]" ) .negativeText(R.string.dialog_button_dismiss) @@ -227,7 +231,13 @@ abstract class ColorSelectBaseActivity : BaseActivity() { .show() return null } - val targetItemIndex = Pref.getInt(KEY_SELECTED_COLOR_ITEM_ID, -1) + val defaultTargetItemIndex = when { + targetLibraryId == COLOR_LIBRARY_ID_DEFAULT -> { + targetLibrary.colors.find { getColor(it.colorRes) == ThemeColorManager.colorPrimary }?.itemId ?: SELECT_NONE + } + else -> SELECT_NONE + } + val targetItemIndex = Pref.getInt(KEY_SELECTED_COLOR_ITEM_ID, defaultTargetItemIndex) if (targetItemIndex !in targetLibrary.colors.indices) { MaterialDialog.Builder(this) .title(R.string.text_failed_to_locate) @@ -499,7 +509,7 @@ abstract class ColorSelectBaseActivity : BaseActivity() { val customNeutral = ColorInfoDialogManager.CustomColorInfoDialogNeutral( textRes = R.string.dialog_button_use_palette, colorRes = R.color.dialog_button_hint, - onNeutralCallback = object: NeutralButtonCallback{ + onNeutralCallback = object : NeutralButtonCallback { override fun onClick(dialog: MaterialDialog, which: DialogAction, color: Int) { parentDialog?.dismiss() dialog.dismiss() diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt index cfd6fbd1..a019e805 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt @@ -11,11 +11,12 @@ import com.afollestad.materialdialogs.MaterialDialog import com.jaredrummler.android.widget.AnimatedSvgView import de.psdev.licensesdialog.LicenseResolver import de.psdev.licensesdialog.LicensesDialog +import org.autojs.autojs.network.UpdateChecker +import org.autojs.autojs.network.UpdateChecker.PromptMode import org.autojs.autojs.ui.BaseActivity import org.autojs.autojs.ui.common.NotAskAgainDialog import org.autojs.autojs.util.ClipboardUtils import org.autojs.autojs.util.DeviceUtils -import org.autojs.autojs.util.UpdateUtils import org.autojs.autojs.util.ViewUtils import org.autojs.autojs6.BuildConfig import org.autojs.autojs6.R @@ -159,7 +160,9 @@ open class AboutActivity : BaseActivity() { } private fun checkForUpdates() { - UpdateUtils.getDialogChecker(this).checkNow() + UpdateChecker.Builder(this) + .setPromptMode(PromptMode.DIALOG) + .build().checkNow() } private fun startFeedbackActivity() { diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt index 7034b898..329f4551 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt +++ b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt @@ -4,10 +4,10 @@ import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.util.AttributeSet -import androidx.preference.Preference.SummaryProvider import org.autojs.autojs.core.pref.Pref +import org.autojs.autojs.network.UpdateChecker +import org.autojs.autojs.network.UpdateChecker.PromptMode import org.autojs.autojs.theme.preference.MaterialPreference -import org.autojs.autojs.util.UpdateUtils import org.autojs.autojs6.R /** @@ -31,7 +31,9 @@ class CheckForUpdatesPreference : MaterialPreference, OnSharedPreferenceChangeLi } override fun onClick() { - UpdateUtils.getDialogChecker(prefContext).checkNow() + UpdateChecker.Builder(prefContext) + .setPromptMode(PromptMode.DIALOG) + .build().checkNow() super.onClick() } diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesWithLocalVersionIgnoredPreference.kt b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesWithLocalVersionIgnoredPreference.kt new file mode 100644 index 00000000..a96d0fcc --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesWithLocalVersionIgnoredPreference.kt @@ -0,0 +1,30 @@ +package org.autojs.autojs.ui.settings + +import android.content.Context +import android.util.AttributeSet +import org.autojs.autojs.network.UpdateChecker +import org.autojs.autojs.network.UpdateChecker.PromptMode +import org.autojs.autojs.theme.preference.MaterialPreference + +/** + * Created by SuperMonster003 on Apr 21, 2025. + */ +class CheckForUpdatesWithLocalVersionIgnoredPreference : MaterialPreference { + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context) : super(context) + + override fun onClick() { + UpdateChecker.Builder(context) + .setPromptMode(PromptMode.DIALOG) + .build() + .checkNow(true) + super.onClick() + } + +} diff --git a/app/src/main/java/org/autojs/autojs/util/BitmapUtils.kt b/app/src/main/java/org/autojs/autojs/util/BitmapUtils.kt index 7f20f976..90280e1e 100644 --- a/app/src/main/java/org/autojs/autojs/util/BitmapUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/BitmapUtils.kt @@ -5,6 +5,7 @@ import android.graphics.Canvas import android.graphics.Matrix import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import androidx.core.graphics.createBitmap /** * Created by Stardust on Apr 22, 2017. @@ -29,7 +30,7 @@ object BitmapUtils { @JvmStatic fun drawableToBitmap(drawable: Drawable): Bitmap = when (drawable) { is BitmapDrawable -> drawable.bitmap - else -> Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + else -> createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight) .also { drawable.draw(Canvas(it)) } } diff --git a/app/src/main/java/org/autojs/autojs/util/TextUtils.kt b/app/src/main/java/org/autojs/autojs/util/TextUtils.kt index f7998d36..09355e25 100644 --- a/app/src/main/java/org/autojs/autojs/util/TextUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/TextUtils.kt @@ -1,5 +1,8 @@ package org.autojs.autojs.util +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned import org.commonmark.ext.heading.anchor.HeadingAnchorExtension import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer @@ -19,4 +22,60 @@ object TextUtils { @JvmStatic fun renderMarkdown(markdown: String): String = mParser.parse(markdown).let { mHtmlRender.render(it) } + @JvmStatic + @JvmOverloads + fun splitSpanned(spanned: Spanned, delimiter: String = "\n"): List { + val result = mutableListOf() + val fullText = spanned.toString() + var start = 0 + + while (true) { + // 找到分隔符位置 + val index = fullText.indexOf(delimiter, start) + // 如果没有更多分隔符, 则 end 指向文本结尾 + val end = if (index == -1) fullText.length else index + // 截取当前部分文本 + val partText = spanned.subSequence(start, end) + val spannablePart = SpannableString(partText) + + // 提取在当前区间内的所有 span 对象 + val spans = spanned.getSpans(start, end, Any::class.java) + for (span in spans) { + val spanStart = spanned.getSpanStart(span) + val spanEnd = spanned.getSpanEnd(span) + + // 只复制完全落在当前部分中的 span + if (spanStart >= start && spanEnd <= end) { + spannablePart.setSpan( + span, + spanStart - start, + spanEnd - start, + spanned.getSpanFlags(span) + ) + } + } + result.add(spannablePart) + + // 如果已经到达文本末尾则结束循环 + if (index == -1) { + break + } + start = index + delimiter.length + } + return result + } + + @JvmStatic + @JvmOverloads + fun joinSpanned(spannedList: List, delimiter: String = "\n"): Spanned { + val builder = SpannableStringBuilder() + spannedList.forEachIndexed { index, spanned -> + builder.append(spanned) + if (index != spannedList.lastIndex) { + builder.append(delimiter) + } + } + return builder + } + } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt b/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt index 8155b541..c12b1d9a 100644 --- a/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt @@ -2,23 +2,18 @@ package org.autojs.autojs.util import android.content.Context import android.content.Intent -import android.net.Uri -import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import com.afollestad.materialdialogs.MaterialDialog -import okhttp3.ResponseBody -import org.autojs.autojs.annotation.ScriptInterface import org.autojs.autojs.app.DialogUtils -import org.autojs.autojs.network.UpdateChecker -import org.autojs.autojs.network.UpdateChecker.PromptMode -import org.autojs.autojs.network.entity.VersionInfo import org.autojs.autojs.core.pref.Pref import org.autojs.autojs.core.pref.Pref.isAutoCheckForUpdatesEnabled import org.autojs.autojs.core.pref.Pref.lastNoNewerUpdatesTimestamp import org.autojs.autojs.core.pref.Pref.lastUpdatesAutoCheckedTimestamp import org.autojs.autojs.core.pref.Pref.lastUpdatesPostponedTimestamp -import org.autojs.autojs.core.pref.Pref.refreshLastUpdatesAutoCheckedTimestamp -import org.autojs.autojs.tool.SimpleObserver +import org.autojs.autojs.network.UpdateChecker +import org.autojs.autojs.network.UpdateChecker.PromptMode +import org.autojs.autojs.network.entity.VersionInfo import org.autojs.autojs6.R /** @@ -26,52 +21,15 @@ import org.autojs.autojs6.R */ object UpdateUtils { - const val BASE_URL_RAW = "https://raw.githubusercontent.com/" - const val BASE_URL = "https://github.com/" - private const val RELATIVE_URL_RAW = "/SuperMonster003/AutoJs6/master/version.properties" - private const val RELATIVE_URL = "/SuperMonster003/AutoJs6/blob/master/version.properties" - private val ignoredVersions: LinkedHashSet by lazy { Pref.getLinkedHashSet(R.string.key_ignored_updates) } - @ScriptInterface - fun getDialogChecker(context: Context, url: String?, callback: SimpleObserver?): UpdateChecker { - return getBuilder(context, url, callback) - .setPromptMode(PromptMode.DIALOG) - .build() - } - - fun getDialogChecker(context: Context): UpdateChecker { - return getDialogChecker(context, null, null) - } - @JvmStatic - fun getSnackbarChecker(context: Context, view: View): UpdateChecker { - return getBuilder(context, view) - .setPromptMode(PromptMode.SNACKBAR) - .build() - } - - private fun getBuilder(context: Context, url: String?, callback: SimpleObserver?): UpdateChecker.Builder { - return UpdateChecker.Builder(context) - .setBaseUrl(BASE_URL) - .setUrl(url ?: RELATIVE_URL) - .setCallback(callback) - } - - private fun getBuilder(context: Context, view: View): UpdateChecker.Builder { - return UpdateChecker.Builder(context, view) - .setBaseUrl(BASE_URL) - .setUrl(RELATIVE_URL) - .setCallback(null) - } - - @JvmStatic - fun openUrl(context: Context, url: String?) { + fun openUrl(context: Context, url: String) { context.startActivity( Intent(Intent.ACTION_VIEW) - .setData(Uri.parse(url)) + .setData(url.toUri()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) } @@ -120,7 +78,7 @@ object UpdateUtils { .title(R.string.text_version_info) .content( "${context.getString(R.string.text_version_name)}: ${simpleInfo.versionName}\n" + - "${context.getString(R.string.text_version_code)}: ${simpleInfo.versionCode}" + "${context.getString(R.string.text_version_code)}: ${simpleInfo.versionCode}" ) .neutralText(R.string.dialog_button_remove) .neutralColorRes(R.color.dialog_button_warn) @@ -179,8 +137,10 @@ object UpdateUtils { if (isAutoCheckForUpdatesEnabled) { val checker = IntervalChecker() if (checker.isBeyondNoNewer && checker.isBeyondPostponed && checker.isBeyondAutoChecked) { - getSnackbarChecker(activity, activity.findViewById(snackbarViewIdRes)).checkNow() - refreshLastUpdatesAutoCheckedTimestamp() + UpdateChecker.Builder(activity.findViewById(snackbarViewIdRes)) + .setPromptMode(PromptMode.SNACKBAR) + .build().checkNow() + Pref.refreshLastUpdatesAutoCheckedTimestamp() } } } diff --git a/app/src/main/java/org/autojs/autojs/util/UrlUtils.kt b/app/src/main/java/org/autojs/autojs/util/UrlUtils.kt new file mode 100644 index 00000000..d96b1f39 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/util/UrlUtils.kt @@ -0,0 +1,16 @@ +package org.autojs.autojs.util + +import android.content.Context +import org.autojs.autojs6.R + +object UrlUtils { + + @JvmStatic + private fun ensureUrl(context: Context, url: String?) { + val regexUrl = "^(https?|ftp|file)://[-a-zA-Z\\d+&@#/%?=~_|!:,.;]*[-a-zA-Z\\d+&@#/%=~_|]" + require(url != null && url.matches(regexUrl.toRegex())) { + context.getString(R.string.error_illegal_url_argument) + } + } + +} \ 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 1d771f88..cf07b7b7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -993,5 +993,15 @@ إعدادات النظام الآمنة ، التي تحتوي على تفضيلات النظام التي يمكن أن تقرأها التطبيقات ولكن لا يُسمح لها بالكتابة.\nهذه هي لتفضيلات يجب على المستخدم تعديلها بشكل صريح من خلال واجهة المستخدم لتطبيق النظام.\nمع إذن إعدادات النظام الآمن ، يمكن للتطبيقات العادية تعديل الإعدادات الآمنة مباشرة (مثل خدمة إمكانية الوصول). كتابة إعدادات النظام المسار المُحلّ لـ \"%1$s\" لا يمكن أن يكون null + التحقق من وجود تحديثات (تجاهل النسخة المحلية) + الحصول مباشرة على آخر نسخة عن بُعد عند التحقق من التحديثات + فشل في استرداد معلومات الإصدار + فشل في استرداد معلومات الإصدار للغة المعروضة حالياً، يُرجى المحاولة مرة أخرى لاحقاً.\n\nسيتم حالياً عرض معلومات الإصدار بلغة \"简体中文 (zh-Hans)\" مؤقتاً. + تلميح + إضافة جديدة + تصليح + تحسين + التبعيات + تعديلات على إصدارات بعض التبعيات أو المكتبات المحلية diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 257f641f..88943a03 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -988,5 +988,15 @@ Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service). Write system settings Resolved path for \"%1$s\" cannot be null + Check for updates (ignore the local version) + Fetch the latest remote version directly when checking for updates + Failed to retrieve release notes + Failed to retrieve the release notes for the currently displayed language. Please try again later.\n\nCurrently, the release notes for \"简体中文 (zh-Hans)\" are temporarily displayed. + Hint + Feature + Fix + Improvement + Dependency + Some dependency or local library version adjustments diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c2156db6..d7098a43 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -991,5 +991,15 @@ Ajustes de seguridad del sistema, que contienen preferencias del sistema que las aplicaciones pueden leer pero no pueden escribir.\nSe trata de preferencias que el usuario debe modificar explícitamente a través de la interfaz de usuario de una aplicación del sistema.\nCon el permiso de configuración segura del sistema, las aplicaciones normales pueden modificar directamente la configuración segura (como el servicio de accesibilidad). Escribir la configuración del sistema La ruta resuelta para \"%1$s\" no puede ser nula + Buscar actualizaciones (ignorar la versión local) + Obtener directamente la última versión remota al buscar actualizaciones + No se pudo obtener la información de la versión + No se pudo obtener la información de la versión correspondiente al idioma actual, puede intentarlo más tarde.\n\nActualmente se mostrará temporalmente la información de la versión en \"简体中文 (zh-Hans)\". + Sugerencia + Nuevo + Corrección + Mejora + Dependencia + Ajustes en algunas dependencias o versiones de bibliotecas locales diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index baeedd57..182813b7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -409,7 +409,7 @@ Type inconnu Changer de répertoire de travail Vérifier les mises à jour - Checking for updates... + Recherche de mises à jour... Ville ou localité Nom de la classe ou du paquet Clear @@ -991,5 +991,15 @@ Paramètres de sécurité du système, contenant les préférences du système que les applications peuvent lire mais ne sont pas autorisées à écrire.\nIl s\'agit des préférences que l\'utilisateur doit explicitement modifier par le biais de l\'interface utilisateur d\'une application système.\nAvec l\'autorisation de paramètres de sécurité du système, les applications normales peuvent directement modifier les paramètres de sécurité (comme le service d\'accessibilité). Écrire les paramètres système Le chemin résolu pour \"%1$s\" ne peut pas être nul + Vérifier les mises à jour (ignorer la version locale) + Récupérer directement la dernière version distante lors de la vérification des mises à jour + Échec de la récupération des informations de version + Échec de la récupération des informations de version correspondant à la langue actuellement affichée, veuillez réessayer ultérieurement.\n\nPour l\'instant, les informations de version en \"简体中文 (zh-Hans)\" seront affichées temporairement. + Astuce + Fonctionnalité + Correction + Amélioration + Dépendance + Certaines dépendances ou versions de bibliothèques locales ont été ajustées diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b3ca5fd9..b040b727 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -992,5 +992,15 @@ アプリケーションが読み取ることはできるが, 書き込むことはできないシステム環境設定を含む, 安全なシステム設定です.\nこれは, ユーザーがシステムアプリの UI を通じて明示的に変更する必要がある環境設定のためのものです.\nセキュアなシステム設定を許可すると, 通常のアプリケーションはセキュアな設定 (アクセシビリティサービスなど) を直接変更できるようになります システム設定の書き込み \"%1$s\" の解決済みパスは null にできません + アップデートの確認 (ローカルバージョンを無視する) + アップデートを確認する際に, 最新のリモートバージョンを直接取得する + バージョン情報の取得に失敗しました + 現在表示中の言語に対応するバージョン情報の取得に失敗しました. しばらくしてから再度お試しください.\n\n現在, 一時的に \"简体中文 (zh-Hans)\" のバージョン情報を表示しています. + ヒント + 新機能 + 修正 + 改善 + 依存関係 + 一部の依存関係またはローカルライブラリのバージョン調整 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 07f6ad15..d2c5e8c9 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -993,5 +993,15 @@ 애플리케이션이 읽을 수 있지만 쓸 수없는 시스템 환경 설정을 포함하는 보안 시스템 설정.\n이들은 사용자가 시스템 앱의 UI 를 통해 명시 적으로 수정 해야하는 선호도입니다.\n보안 시스템 설정 권한을 사용하면 일반 애플리케이션이 보안 설정 (예: 접근성 서비스)을 직접 수정할 수 있습니다. 시스템 설정을 작성하십시오 \"%1$s\" 에 대한 해결된 경로는 null 일 수 없습니다 + 업데이트 확인 (로컬 버전 무시) + 업데이트 확인 시 최신 원격 버전을 직접 가져옵니다 + 버전 정보를 가져오지 못했습니다 + 현재 표시 언어에 해당하는 버전 정보를 가져오지 못했습니다. 나중에 다시 시도해 주세요.\n\n현재 임시로 \"简体中文 (zh-Hans)\" 언어의 버전 정보가 표시되고 있습니다. + 힌트 + 추가 + 수정 + 개선 + 의존성 + 일부 의존성 또는 로컬 라이브러리 버전 조정 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b77c77e3..e2a9ac3c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -991,5 +991,15 @@ Настройки безопасности системы, содержащие системные предпочтения, которые приложения могут читать, но не имеют права записывать.\nОни предназначены для параметров, которые пользователь должен явно изменить через пользовательский интерфейс системного приложения.\nПри наличии разрешения на безопасные системные настройки обычные приложения могут напрямую изменять безопасные настройки (например, служба доступности). Запись системных настроек Разрешённый путь для \"%1$s\" не может быть равен null + Проверить обновления (игнорировать локальную версию) + При проверке обновлений напрямую получать последнюю удалённую версию + Не удалось получить информацию о версии + Не удалось получить информацию о версии для текущего выбранного языка, повторите попытку позже.\n\nВ данный момент временно отображается информация о версии для языка \"简体中文 (zh-Hans)\". + Подсказка + Новое + Исправление + Улучшение + Зависимость + Изменения версий некоторых зависимостей или локальных библиотек diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 36a2ad6e..656c254b 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -989,5 +989,15 @@ 安全設置包含應用程序可讀但不可寫入的設置選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設置權限\" 後, 普通應用可直接修改上述安全設置 (例如無障礙服務). 修改系統設置 解析後的路徑 \"%1$s\" 不可為 null + 檢查更新 (忽略本地版本) + 檢查更新時, 直接從遠程獲取最新版本 + 獲取版本信息失敗 + 獲取當前顯示語言對應的版本信息失敗, 可稍後再次嘗試.\n\n目前將臨時顯示 \"简体中文 (zh-Hans)\" 語言的版本信息. + 提示 + 新增 + 修復 + 優化 + 依賴 + 部分依賴或本地庫版本調整 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 95112c91..29cf6410 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -989,5 +989,15 @@ 安全設定包含應用程式可讀但不可寫入的設定選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設定許可權\" 後, 普通應用可直接修改上述安全設定 (例如無障礙服務). 修改系統設定 解析後的路徑 \"%1$s\" 不可為 null + 檢查更新 (忽略本地版本) + 檢查更新時, 直接從遠端獲取最新版本 + 獲取版本資訊失敗 + 獲取當前顯示語言對應的版本資訊失敗, 可稍後再次嘗試.\n\n目前將臨時顯示 \"简体中文 (zh-Hans)\" 語言的版本資訊. + 提示 + 新增 + 修復 + 最佳化 + 依賴 + 部分依賴或本地庫版本調整 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 7ddaa395..fb2ceb03 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -989,5 +989,15 @@ 安全设置包含应用程序可读但不可写入的设置选项, 这些选项只能由 UI 或系统级别应用修改.\n被授予 \"修改安全设置权限\" 后, 普通应用可直接修改上述安全设置 (例如无障碍服务). 修改系统设置 解析后的路径 \"%1$s\" 不可为 null + 检查更新 (忽略本地版本) + 检查更新时, 直接从远程获取最新版本 + 获取版本信息失败 + 获取当前显示语言对应的版本信息失败, 可稍后再次尝试.\n\n目前将临时显示 \"简体中文 (zh-Hans)\" 语言的版本信息. + 提示 + 新增 + 修复 + 优化 + 依赖 + 部分依赖或本地库版本调整 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 556b2a7c..c0a7894d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ key_$_auto_night_mode_enabled key_$_change_working_dir key_$_check_for_updates + key_$_check_for_updates_while_ignoring_the_local_version key_$_client_socket_normally_closed key_$_color_select_activity_legacy_layout key_$_dialog_check_display_over_other_apps @@ -1218,5 +1219,15 @@ Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service). Write system settings Resolved path for \"%1$s\" cannot be null + Check for updates (ignore the local version) + Fetch the latest remote version directly when checking for updates + Failed to retrieve release notes + Failed to retrieve the release notes for the currently displayed language. Please try again later.\n\nCurrently, the release notes for \"简体中文 (zh-Hans)\" are temporarily displayed. + Hint + Feature + Fix + Improvement + Dependency + Some dependency or local library version adjustments diff --git a/app/src/main/res/xml/fragment_developer_options.xml b/app/src/main/res/xml/fragment_developer_options.xml index b960c889..3d572719 100644 --- a/app/src/main/res/xml/fragment_developer_options.xml +++ b/app/src/main/res/xml/fragment_developer_options.xml @@ -1,15 +1,22 @@ - + + app:layout="@layout/preference_category_custom" + app:title="@string/text_updates"> + app:key="@string/key_updates_checked_states_cleared" + app:layout="@layout/preference_custom" + app:summary="@string/text_clear_updates_checked_states_description" + app:title="@string/text_clear_updates_checked_states" /> + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 7277762e..7f2aa6b3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -219,7 +219,7 @@ pluginManagement { val intelliJIdea = object : Platform( name = "IntelliJIdea", vendor = "Jetbrains", androidVersionMap = mapOf( - "2025.1" to "8.9.1", /* Apr 12, 2025. */ + "2025.1" to "8.9.2", /* Apr 23, 2025. */ "2024.3.1" to "8.7.3", /* Dec 10, 2024. */ "2024.3" to "8.7.0-rc01", /* Nov 15, 2024. */ "2024.2" to "8.5.2", /* Aug 13, 2024. */ diff --git a/version.properties b/version.properties index 940dc2b4..338e0c79 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Sat Apr 19 19:56:25 CST 2025 -BUILD_TIME=1745063785708 +#Sat Apr 26 11:16:55 CST 2025 +BUILD_TIME=1745637415532 COMPILE_SDK_VERSION=35 JAVA_VERSION=23 JAVA_VERSION_MIN_RADICAL=0 @@ -17,6 +17,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 TARGET_SDK_VERSION=35 TARGET_SDK_VERSION_INRT=29 -VERSION_BUILD=3154 +VERSION_BUILD=3167 VERSION_NAME=6.6.3 Alpha VSCODE_EXT_REQUIRED_VERSION=1.0.8