6.6.3 - Alpha - 版本更新信息支持多语言显示

This commit is contained in:
SuperMonster003
2025-04-26 11:23:54 +08:00
parent 174a52b5a2
commit 09190f44f3
45 changed files with 760 additions and 323 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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].

View File

@@ -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<ResponseBody> mCallback;
private final Executor mGitHubExecutor = Executors.newSingleThreadExecutor();
private UpdateCheckerApi checkerApi;
private UpdateChecker(Context context, View view, String baseUrl, String url, PromptMode promptMode, SimpleObserver<ResponseBody> callback) {
private UpdateChecker(Context context, View view, PromptMode promptMode, SimpleObserver<ResponseBody> 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<ResponseBody> 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<ResponseBody> 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<GHAsset> 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<Spanned> 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<Spanned> 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<String> 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 = "<li><code>" +
labelImprovement +
"</code> " +
dependenciesSummary +
" <em><a href=\"" +
markdownUrl + "#" + anchor +
"\" rel=\"nofollow\"><code>" +
"CHANGELOG.md" +
"</code></a></em></li>";
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<String> 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<String> 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<GHAsset> 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<ResponseBody> 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 {
}
}
}

View File

@@ -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<ResponseBody> checkForUpdates(@Url String url);
Observable<ResponseBody> streamingUrl(@Url String url);
}
}

View File

@@ -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)

View File

@@ -37,16 +37,16 @@ class VersionInfo : ExtendedVersionInfo {
val regexVersionName = "$versionNameKey(?:</\\w+>)?=([\\w.]+)".toRegex()
val regexVersionCode = "$versionCodeKey(?:</\\w+>)?=(\\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
}
}

View File

@@ -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())
}
}

View File

@@ -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()

View File

@@ -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() {

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -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)) }
}

View File

@@ -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<Spanned> {
val result = mutableListOf<SpannableString>()
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<Spanned>, delimiter: String = "\n"): Spanned {
val builder = SpannableStringBuilder()
spannedList.forEachIndexed { index, spanned ->
builder.append(spanned)
if (index != spannedList.lastIndex) {
builder.append(delimiter)
}
}
return builder
}
}

View File

@@ -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<String> by lazy {
Pref.getLinkedHashSet(R.string.key_ignored_updates)
}
@ScriptInterface
fun getDialogChecker(context: Context, url: String?, callback: SimpleObserver<ResponseBody>?): 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<ResponseBody>?): 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()
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -993,5 +993,15 @@
<string name="text_write_secure_settings_description">إعدادات النظام الآمنة ، التي تحتوي على تفضيلات النظام التي يمكن أن تقرأها التطبيقات ولكن لا يُسمح لها بالكتابة.\nهذه هي لتفضيلات يجب على المستخدم تعديلها بشكل صريح من خلال واجهة المستخدم لتطبيق النظام.\nمع إذن إعدادات النظام الآمن ، يمكن للتطبيقات العادية تعديل الإعدادات الآمنة مباشرة (مثل خدمة إمكانية الوصول).</string>
<string name="text_write_system_settings">كتابة إعدادات النظام</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">المسار المُحلّ لـ \"%1$s\" لا يمكن أن يكون null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">التحقق من وجود تحديثات (تجاهل النسخة المحلية)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">الحصول مباشرة على آخر نسخة عن بُعد عند التحقق من التحديثات</string>
<string name="error_failed_to_retrieve_released_notes">فشل في استرداد معلومات الإصدار</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">فشل في استرداد معلومات الإصدار للغة المعروضة حالياً، يُرجى المحاولة مرة أخرى لاحقاً.\n\nسيتم حالياً عرض معلومات الإصدار بلغة \"简体中文 (zh-Hans)\" مؤقتاً.</string>
<string name="changelog_label_hint">تلميح</string>
<string name="changelog_label_feature">إضافة جديدة</string>
<string name="changelog_label_fix">تصليح</string>
<string name="changelog_label_improvement">تحسين</string>
<string name="changelog_label_dependency">التبعيات</string>
<string name="text_changelog_item_dependency">تعديلات على إصدارات بعض التبعيات أو المكتبات المحلية</string>
</resources>

View File

@@ -988,5 +988,15 @@
<string name="text_write_secure_settings_description">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).</string>
<string name="text_write_system_settings">Write system settings</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">Resolved path for \"%1$s\" cannot be null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">Check for updates (ignore the local version)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">Fetch the latest remote version directly when checking for updates</string>
<string name="error_failed_to_retrieve_released_notes">Failed to retrieve release notes</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">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.</string>
<string name="changelog_label_hint">Hint</string>
<string name="changelog_label_feature">Feature</string>
<string name="changelog_label_fix">Fix</string>
<string name="changelog_label_improvement">Improvement</string>
<string name="changelog_label_dependency">Dependency</string>
<string name="text_changelog_item_dependency">Some dependency or local library version adjustments</string>
</resources>

View File

@@ -991,5 +991,15 @@
<string name="text_write_secure_settings_description">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).</string>
<string name="text_write_system_settings">Escribir la configuración del sistema</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">La ruta resuelta para \"%1$s\" no puede ser nula</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">Buscar actualizaciones (ignorar la versión local)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">Obtener directamente la última versión remota al buscar actualizaciones</string>
<string name="error_failed_to_retrieve_released_notes">No se pudo obtener la información de la versión</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">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)\".</string>
<string name="changelog_label_hint">Sugerencia</string>
<string name="changelog_label_feature">Nuevo</string>
<string name="changelog_label_fix">Corrección</string>
<string name="changelog_label_improvement">Mejora</string>
<string name="changelog_label_dependency">Dependencia</string>
<string name="text_changelog_item_dependency">Ajustes en algunas dependencias o versiones de bibliotecas locales</string>
</resources>

View File

@@ -409,7 +409,7 @@
<string name="text_captured_window_info_type_unknown">Type inconnu</string>
<string name="text_change_working_dir">Changer de répertoire de travail</string>
<string name="text_check_for_updates">Vérifier les mises à jour</string>
<string name="text_checking_update" tools:ignore="TypographyEllipsis">Checking for updates...</string>
<string name="text_checking_update" tools:ignore="TypographyEllipsis">Recherche de mises à jour...</string>
<string name="text_city_or_locality">Ville ou localité</string>
<string name="text_class_or_package_name">Nom de la classe ou du paquet</string>
<string name="text_clear">Clear</string>
@@ -991,5 +991,15 @@
<string name="text_write_secure_settings_description">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é).</string>
<string name="text_write_system_settings">Écrire les paramètres système</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">Le chemin résolu pour \"%1$s\" ne peut pas être nul</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">Vérifier les mises à jour (ignorer la version locale)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">Récupérer directement la dernière version distante lors de la vérification des mises à jour</string>
<string name="error_failed_to_retrieve_released_notes">Échec de la récupération des informations de version</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">É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.</string>
<string name="changelog_label_hint">Astuce</string>
<string name="changelog_label_feature">Fonctionnalité</string>
<string name="changelog_label_fix">Correction</string>
<string name="changelog_label_improvement">Amélioration</string>
<string name="changelog_label_dependency">Dépendance</string>
<string name="text_changelog_item_dependency">Certaines dépendances ou versions de bibliothèques locales ont été ajustées</string>
</resources>

View File

@@ -992,5 +992,15 @@
<string name="text_write_secure_settings_description">アプリケーションが読み取ることはできるが, 書き込むことはできないシステム環境設定を含む, 安全なシステム設定です.\nこれは, ユーザーがシステムアプリの UI を通じて明示的に変更する必要がある環境設定のためのものです.\nセキュアなシステム設定を許可すると, 通常のアプリケーションはセキュアな設定 (アクセシビリティサービスなど) を直接変更できるようになります</string>
<string name="text_write_system_settings">システム設定の書き込み</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">\"%1$s\" の解決済みパスは null にできません</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">アップデートの確認 (ローカルバージョンを無視する)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">アップデートを確認する際に, 最新のリモートバージョンを直接取得する</string>
<string name="error_failed_to_retrieve_released_notes">バージョン情報の取得に失敗しました</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">現在表示中の言語に対応するバージョン情報の取得に失敗しました. しばらくしてから再度お試しください.\n\n現在, 一時的に \"简体中文 (zh-Hans)\" のバージョン情報を表示しています.</string>
<string name="changelog_label_hint">ヒント</string>
<string name="changelog_label_feature">新機能</string>
<string name="changelog_label_fix">修正</string>
<string name="changelog_label_improvement">改善</string>
<string name="changelog_label_dependency">依存関係</string>
<string name="text_changelog_item_dependency">一部の依存関係またはローカルライブラリのバージョン調整</string>
</resources>

View File

@@ -993,5 +993,15 @@
<string name="text_write_secure_settings_description">애플리케이션이 읽을 수 있지만 쓸 수없는 시스템 환경 설정을 포함하는 보안 시스템 설정.\n이들은 사용자가 시스템 앱의 UI 를 통해 명시 적으로 수정 해야하는 선호도입니다.\n보안 시스템 설정 권한을 사용하면 일반 애플리케이션이 보안 설정 (예: 접근성 서비스)을 직접 수정할 수 있습니다.</string>
<string name="text_write_system_settings">시스템 설정을 작성하십시오</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">\"%1$s\" 에 대한 해결된 경로는 null 일 수 없습니다</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">업데이트 확인 (로컬 버전 무시)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">업데이트 확인 시 최신 원격 버전을 직접 가져옵니다</string>
<string name="error_failed_to_retrieve_released_notes">버전 정보를 가져오지 못했습니다</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">현재 표시 언어에 해당하는 버전 정보를 가져오지 못했습니다. 나중에 다시 시도해 주세요.\n\n현재 임시로 \"简体中文 (zh-Hans)\" 언어의 버전 정보가 표시되고 있습니다.</string>
<string name="changelog_label_hint">힌트</string>
<string name="changelog_label_feature">추가</string>
<string name="changelog_label_fix">수정</string>
<string name="changelog_label_improvement">개선</string>
<string name="changelog_label_dependency">의존성</string>
<string name="text_changelog_item_dependency">일부 의존성 또는 로컬 라이브러리 버전 조정</string>
</resources>

View File

@@ -991,5 +991,15 @@
<string name="text_write_secure_settings_description">Настройки безопасности системы, содержащие системные предпочтения, которые приложения могут читать, но не имеют права записывать.\nОни предназначены для параметров, которые пользователь должен явно изменить через пользовательский интерфейс системного приложения.\nПри наличии разрешения на безопасные системные настройки обычные приложения могут напрямую изменять безопасные настройки (например, служба доступности).</string>
<string name="text_write_system_settings">Запись системных настроек</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">Разрешённый путь для \"%1$s\" не может быть равен null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">Проверить обновления (игнорировать локальную версию)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">При проверке обновлений напрямую получать последнюю удалённую версию</string>
<string name="error_failed_to_retrieve_released_notes">Не удалось получить информацию о версии</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">Не удалось получить информацию о версии для текущего выбранного языка, повторите попытку позже.\n\nВ данный момент временно отображается информация о версии для языка \"简体中文 (zh-Hans)\".</string>
<string name="changelog_label_hint">Подсказка</string>
<string name="changelog_label_feature">Новое</string>
<string name="changelog_label_fix">Исправление</string>
<string name="changelog_label_improvement">Улучшение</string>
<string name="changelog_label_dependency">Зависимость</string>
<string name="text_changelog_item_dependency">Изменения версий некоторых зависимостей или локальных библиотек</string>
</resources>

View File

@@ -989,5 +989,15 @@
<string name="text_write_secure_settings_description">安全設置包含應用程序可讀但不可寫入的設置選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設置權限\" 後, 普通應用可直接修改上述安全設置 (例如無障礙服務).</string>
<string name="text_write_system_settings">修改系統設置</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">解析後的路徑 \"%1$s\" 不可為 null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">檢查更新 (忽略本地版本)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">檢查更新時, 直接從遠程獲取最新版本</string>
<string name="error_failed_to_retrieve_released_notes">獲取版本信息失敗</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">獲取當前顯示語言對應的版本信息失敗, 可稍後再次嘗試.\n\n目前將臨時顯示 \"简体中文 (zh-Hans)\" 語言的版本信息.</string>
<string name="changelog_label_hint">提示</string>
<string name="changelog_label_feature">新增</string>
<string name="changelog_label_fix">修復</string>
<string name="changelog_label_improvement">優化</string>
<string name="changelog_label_dependency">依賴</string>
<string name="text_changelog_item_dependency">部分依賴或本地庫版本調整</string>
</resources>

View File

@@ -989,5 +989,15 @@
<string name="text_write_secure_settings_description">安全設定包含應用程式可讀但不可寫入的設定選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設定許可權\" 後, 普通應用可直接修改上述安全設定 (例如無障礙服務).</string>
<string name="text_write_system_settings">修改系統設定</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">解析後的路徑 \"%1$s\" 不可為 null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">檢查更新 (忽略本地版本)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">檢查更新時, 直接從遠端獲取最新版本</string>
<string name="error_failed_to_retrieve_released_notes">獲取版本資訊失敗</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">獲取當前顯示語言對應的版本資訊失敗, 可稍後再次嘗試.\n\n目前將臨時顯示 \"简体中文 (zh-Hans)\" 語言的版本資訊.</string>
<string name="changelog_label_hint">提示</string>
<string name="changelog_label_feature">新增</string>
<string name="changelog_label_fix">修復</string>
<string name="changelog_label_improvement">最佳化</string>
<string name="changelog_label_dependency">依賴</string>
<string name="text_changelog_item_dependency">部分依賴或本地庫版本調整</string>
</resources>

View File

@@ -989,5 +989,15 @@
<string name="text_write_secure_settings_description">安全设置包含应用程序可读但不可写入的设置选项, 这些选项只能由 UI 或系统级别应用修改.\n被授予 \"修改安全设置权限\" 后, 普通应用可直接修改上述安全设置 (例如无障碍服务).</string>
<string name="text_write_system_settings">修改系统设置</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">解析后的路径 \"%1$s\" 不可为 null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">检查更新 (忽略本地版本)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">检查更新时, 直接从远程获取最新版本</string>
<string name="error_failed_to_retrieve_released_notes">获取版本信息失败</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">获取当前显示语言对应的版本信息失败, 可稍后再次尝试.\n\n目前将临时显示 \"简体中文 (zh-Hans)\" 语言的版本信息.</string>
<string name="changelog_label_hint">提示</string>
<string name="changelog_label_feature">新增</string>
<string name="changelog_label_fix">修复</string>
<string name="changelog_label_improvement">优化</string>
<string name="changelog_label_dependency">依赖</string>
<string name="text_changelog_item_dependency">部分依赖或本地库版本调整</string>
</resources>

View File

@@ -71,6 +71,7 @@
<string name="key_auto_night_mode_enabled" translatable="false">key_$_auto_night_mode_enabled</string>
<string name="key_change_working_dir" translatable="false">key_$_change_working_dir</string>
<string name="key_check_for_updates" translatable="false">key_$_check_for_updates</string>
<string name="key_check_for_updates_while_ignoring_the_local_version" translatable="false">key_$_check_for_updates_while_ignoring_the_local_version</string>
<string name="key_client_socket_normally_closed" translatable="false">key_$_client_socket_normally_closed</string>
<string name="key_color_select_activity_legacy_layout" translatable="false">key_$_color_select_activity_legacy_layout</string>
<string name="key_dialog_check_display_over_other_apps" translatable="false">key_$_dialog_check_display_over_other_apps</string>
@@ -1218,5 +1219,15 @@
<string name="text_write_secure_settings_description">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).</string>
<string name="text_write_system_settings">Write system settings</string>
<string name="error_resolved_path_for_a_relative_path_cannot_be_null">Resolved path for \"%1$s\" cannot be null</string>
<string name="text_check_for_updates_while_ignoring_the_local_version">Check for updates (ignore the local version)</string>
<string name="text_check_for_updates_while_ignoring_the_local_version_description">Fetch the latest remote version directly when checking for updates</string>
<string name="error_failed_to_retrieve_released_notes">Failed to retrieve release notes</string>
<string name="content_failed_to_retrieve_released_notes_of_current_language_with_zh_hans_fallback">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.</string>
<string name="changelog_label_hint">Hint</string>
<string name="changelog_label_feature">Feature</string>
<string name="changelog_label_fix">Fix</string>
<string name="changelog_label_improvement">Improvement</string>
<string name="changelog_label_dependency">Dependency</string>
<string name="text_changelog_item_dependency">Some dependency or local library version adjustments</string>
</resources>

View File

@@ -1,15 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.autojs.autojs.theme.preference.ThemeColorPreferenceCategory
app:layout="@layout/preference_category_custom"
app:title="@string/text_updates">
app:layout="@layout/preference_category_custom"
app:title="@string/text_updates">
<org.autojs.autojs.ui.settings.ClearUpdatesCheckedStatesPreference
app:layout="@layout/preference_custom"
app:key="@string/key_updates_checked_states_cleared"
app:title="@string/text_clear_updates_checked_states"
app:summary="@string/text_clear_updates_checked_states_description" />
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" />
<org.autojs.autojs.ui.settings.CheckForUpdatesWithLocalVersionIgnoredPreference
app:key="@string/key_check_for_updates_while_ignoring_the_local_version"
app:layout="@layout/preference_custom"
app:summary="@string/text_check_for_updates_while_ignoring_the_local_version_description"
app:title="@string/text_check_for_updates_while_ignoring_the_local_version" />
</org.autojs.autojs.theme.preference.ThemeColorPreferenceCategory>