diff --git a/README.md b/README.md index c566faff..ef14345d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- AF_Banner + AF_Banner

Android 平台支持无障碍服务的 JavaScript 自动化工具

@@ -16,7 +16,7 @@ Rhino Android Studio
- Created + Created GitHub Code Size GitHub License

@@ -104,6 +104,19 @@ [comment]: <> "Version history only shows last 3 versions" +# v6.1.1 + +###### 2022/05/31 + +* `新增` 检查更新/下载更新/更新提示功能 (参阅 设置页面) (暂不支持安卓 7.x 系统) +* `修复` 应用在安卓 10 系统无法读写外部存储的问题 _[`issue #17`](https://github.com/SuperMonster003/AutoJs6/issues/17)_ +* `修复` 编辑器页面长按时可能导致应用崩溃的问题 _[`issue #18`](https://github.com/SuperMonster003/AutoJs6/issues/18)_ +* `修复` 编辑器页面长按菜单 "删除行" 和 "复制行" 功能无效的问题 +* `修复` 编辑器页面选项菜单中 "粘贴" 功能缺失的问题 +* `优化` 部分异常消息字符串资源化 (en / zh) +* `优化` 调整内容未保存对话框的按钮布局并增加颜色区分 +* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v611)_ + # v6.1.0 ###### 2022/05/26 - 包名变更 谨慎升级 @@ -150,7 +163,7 @@ * `优化` 保存 ImageWrapper 所有实例弱引用并在脚本结束时自动回收 (实验性) * `优化` 附加 CircleImageView 版本 3.1.0 * `优化` 升级 Rhino 引擎版本 1.7.14 -> 1.7.15-snapshot -* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v610)) +* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v610)_ # v6.0.3 @@ -169,28 +182,13 @@ * `优化` 主页抽屉增加前台服务状态开关的同步 * `优化` 主页抽屉展开时立即按需同步开关状态 * `优化` 显示指针位置增加状态检测及结果提示 -* `优化` 支持 64 位操作系统 (Ref to TonyJiangWJ) +* `优化` 支持 64 位操作系统 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) * `优化` 悬浮窗初始化时同时应用透明度设置 (无需点击后再应用透明度) * `优化` 重置文件内容时增加是否为示例代码文件的检测并增加结果提示 * `优化` 转移打包插件下载地址 GitHub -> JsDelivr * `优化` 附加 Zeugma Solutions LocaleHelper 版本 1.5.1 * `优化` 降级 Android Material 版本 1.6.0-alpha02 -> 1.5.0 -* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v603)) - -# v6.0.2 - -###### 2022/02/05 - -* `新增` images.bilateralFilter() 双边滤波图像处理方法 -* `修复` 多次调用 toast 只生效最后一次调用的问题 -* `修复` toast.dismiss() 可能无效的问题 -* `修复` 客户端模式及服务端模式开关可能无法正常工作的问题 -* `修复` 客户端模式及服务端模式开关状态不能正常刷新的问题 -* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to TonyJiangWJ) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_ -* `优化` 忽略 sleep() 的 ScriptInterruptedException 异常 -* `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2 -* `优化` 升级 Rhino 引擎版本 1.7.14-snapshot -> 1.7.14 -* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v602)) +* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v603)_ ##### 更多版本历史可参阅 diff --git a/app/build.gradle b/app/build.gradle index de389287..277e68ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { // Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' // Glide diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c542d96..ba22db17 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,10 +2,25 @@ xmlns:tools="http://schemas.android.com/tools" package="org.autojs.autojs"> + + + android:maxSdkVersion="29" /> @@ -42,6 +57,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true" + android:requestLegacyExternalStorage="true" tools:replace="android:label, android:allowBackup" tools:ignore="DataExtractionRules"> diff --git a/app/src/main/assets/doc/CHANGELOG.md b/app/src/main/assets/doc/CHANGELOG.md index d2d633fa..e151c18d 100644 --- a/app/src/main/assets/doc/CHANGELOG.md +++ b/app/src/main/assets/doc/CHANGELOG.md @@ -4,6 +4,20 @@ ****** +# v6.1.1 + +###### 2022/05/31 + +* `新增` 检查更新/下载更新/更新提示功能 (参阅 设置页面) (暂不支持安卓 7.x 系统) +* `修复` 应用在安卓 10 系统无法读写外部存储的问题 _[`issue #17`](https://github.com/SuperMonster003/AutoJs6/issues/17)_ +* `修复` 编辑器页面长按时可能导致应用崩溃的问题 _[`issue #18`](https://github.com/SuperMonster003/AutoJs6/issues/18)_ +* `修复` 编辑器页面长按菜单 "删除行" 和 "复制行" 功能无效的问题 +* `修复` 编辑器页面选项菜单中 "粘贴" 功能缺失的问题 +* `优化` 部分异常消息字符串资源化 (en / zh) +* `优化` 调整内容未保存对话框的按钮布局并增加颜色区分 +* `优化` 附加 github-api 版本 1.306 +* `优化` 替换 retrofit2-rxjava2-adapter 版本 1.0.0 为 adapter-rxjava2 版本 2.9.0 + # v6.1.0 ###### 2022/05/26 - 包名变更 谨慎升级 @@ -82,14 +96,14 @@ * `优化` 主页抽屉增加前台服务状态开关的同步 * `优化` 主页抽屉展开时立即按需同步开关状态 * `优化` 显示指针位置增加状态检测及结果提示 -* `优化` 支持 64 位操作系统 (Ref to TonyJiangWJ) +* `优化` 支持 64 位操作系统 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) * `优化` 悬浮窗初始化时同时应用透明度设置 (无需点击后再应用透明度) * `优化` 重置文件内容时增加是否为示例代码文件的检测并增加结果提示 * `优化` 转移打包插件下载地址 GitHub -> JsDelivr * `优化` 附加 Zeugma Solutions LocaleHelper 版本 1.5.1 * `优化` 降级 Android Material 版本 1.6.0-alpha02 -> 1.5.0 * `优化` 升级 Kotlinx Coroutines 版本 1.6.0-native-mt -> 1.6.0 -* `优化` 升级 OpenCV 版本 3.4.3 -> 4.5.4 -> 4.5.5 (Ref to TonyJiangWJ) +* `优化` 升级 OpenCV 版本 3.4.3 -> 4.5.4 -> 4.5.5 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) * `优化` 升级 Okhttp3 版本 3.10.0 -> 5.0.0-alpha.4 -> 5.0.0-alpha.6 * `优化` 升级 Android Gradle 插件版本 7.2.0-beta01 -> 7.3.0-alpha06 * `优化` 升级 Auto.js-ApkBuilder 版本 1.0.1 -> 1.0.3 @@ -107,7 +121,7 @@ * `修复` toast.dismiss() 可能无效的问题 * `修复` 客户端模式及服务端模式开关可能无法正常工作的问题 * `修复` 客户端模式及服务端模式开关状态不能正常刷新的问题 -* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to TonyJiangWJ) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_ +* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_ * `优化` 忽略 sleep() 的 ScriptInterruptedException 异常 * `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2 * `优化` 升级 Androidx AppCompat 版本 1.4.0 -> 1.4.1 @@ -122,8 +136,8 @@ ###### 2022/01/01 -* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to Auto.js Pro) -* `新增` 内置 base64 模块 (Ref to Auto.js Pro) +* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) +* `新增` 内置 base64 模块 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) * `新增` 增加 isInteger/isNullish/isPlainObject/isPrimitive/isReference 全局方法 * `新增` 增加 polyfill (Object.getOwnPropertyDescriptors) * `新增` 增加 polyfill (Array.prototype.flat) @@ -159,7 +173,7 @@ * `优化` 升级 JSON 模块至 2017-06-12 版本并整合 cycle.js * `优化` 移除 Activity 前置时的自动检查更新功能并移除检查更新相关按钮 * `优化` AppOpsKt#isOpPermissionGranted 内部代码逻辑 -* `优化` ResourceMonitor 使用 ReentrantLock 增强安全性 (Ref to TonyJiangWJ) +* `优化` ResourceMonitor 使用 ReentrantLock 增强安全性 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) * `优化` 使用 Maven Central 等仓库替换 JCenter 仓库 * `优化` 抽离并移除重复的本地库文件 * `优化` 本地化 CrashReport 版本 2.6.6 diff --git a/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js b/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js index 1d0cceb9..b0c7e212 100644 --- a/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js +++ b/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js @@ -17,7 +17,7 @@ console.log('code: ' + morse.code); console.log('pattern: ' + morse.pattern); // Call vibrate() if you need your device play this morse code by vibration. -// zh-CN: Call vibrate() if you need your device play this morse code by vibration. +// zh-CN: 可调用 vibrate() 方法使设备按摩斯电码模式震动. // morse.vibrate(); diff --git a/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java b/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java index 84b0430f..409240e6 100644 --- a/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java +++ b/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java @@ -2,6 +2,7 @@ package com.stardust.theme.preference; import android.content.Context; import android.graphics.Color; +import android.graphics.Typeface; import android.preference.PreferenceCategory; import android.util.AttributeSet; import android.view.View; @@ -54,8 +55,10 @@ public class ThemeColorPreferenceCategory extends PreferenceCategory implements public View onCreateView(ViewGroup parent) { View view = super.onCreateView(parent); mTitleTextView = view.findViewById(android.R.id.title); - if (mColor != Color.TRANSPARENT) + if (mColor != Color.TRANSPARENT) { mTitleTextView.setTextColor(mColor); + } + mTitleTextView.setTypeface(null, Typeface.BOLD); return view; } diff --git a/app/src/main/java/org/autojs/autojs/Pref.java b/app/src/main/java/org/autojs/autojs/Pref.java index 5d151488..6ab196fe 100644 --- a/app/src/main/java/org/autojs/autojs/Pref.java +++ b/app/src/main/java/org/autojs/autojs/Pref.java @@ -3,6 +3,7 @@ package org.autojs.autojs; import android.content.SharedPreferences; import android.os.Environment; +import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; import com.stardust.app.GlobalAppContext; @@ -10,6 +11,9 @@ import com.stardust.autojs.runtime.accessibility.AccessibilityConfig; import org.autojs.autojs.autojs.key.GlobalKeyObserver; import org.autojs.autojs6.R; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import java.io.File; import java.util.Objects; @@ -21,6 +25,7 @@ public class Pref { private static final String KEY_SERVER_ADDRESS = "KEY_SERVER_ADDRESS"; private static final String KEY_FLOATING_MENU_SHOWN = "KEY_FLOATING_MENU_SHOWN"; + private static final String KEY_LAST_UPDATED_CHECKED = "KEY_LAST_UPDATED_CHECKED"; private static final String KEY_APP_LANG_INDEX = "KEY_APP_LANG_INDEX"; private static final String KEY_EDITOR_THEME = "editor.theme"; private static final String KEY_EDITOR_TEXT_SIZE = "editor.textSize"; @@ -51,6 +56,29 @@ public class Pref { return def().getBoolean(getString(R.string.key_use_volume_control_running), true); } + public static boolean isAutoCheckForUpdatesEnabled() { + return def().getBoolean(getString(R.string.key_auto_check_for_updates), true); + } + + public static void refreshLastUpdatesCheckedTimestamp() { + def().edit().putLong(KEY_LAST_UPDATED_CHECKED, System.currentTimeMillis()).apply(); + } + + public static long getLastUpdatesCheckedTimestamp() { + return def().getLong(KEY_LAST_UPDATED_CHECKED, -1); + } + + @Nullable + public static String getLastUpdatesCheckedTimeString() { + long ts = getLastUpdatesCheckedTimestamp(); + if (ts < 0) { + return null; + } + DateTime dt = new DateTime(ts); + DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm"); + return fmt.print(dt); + } + private static String getString(int id) { return GlobalAppContext.getString(id); } diff --git a/app/src/main/java/org/autojs/autojs/build/TinySign.java b/app/src/main/java/org/autojs/autojs/build/TinySign.java index 82e3cd88..6f56fd1a 100644 --- a/app/src/main/java/org/autojs/autojs/build/TinySign.java +++ b/app/src/main/java/org/autojs/autojs/build/TinySign.java @@ -39,10 +39,10 @@ public class TinySign { private static void doDir(String prefix, File dir, ZipOutputStream zos, DigestOutputStream dos, Manifest m) throws IOException { zos.putNextEntry(new ZipEntry(prefix)); zos.closeEntry(); - File[] arr$ = dir.listFiles(); + File[] files = dir.listFiles(); - if (arr$ != null) { - for (File f : arr$) { + if (files != null) { + for (File f : files) { if (f.isFile()) { doFile(prefix + f.getName(), f, zos, dos, m); } else { @@ -72,13 +72,8 @@ public class TinySign { private static Manifest generateSF(Manifest manifest) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("SHA1"); PrintStream print = new PrintStream(new DigestOutputStream(new OutputStream() { - public void write(byte[] arg0) { - } - - public void write(byte[] arg0, int arg1, int arg2) { - } - - public void write(int arg0) { + @Override + public void write(int b) { } }, md), true, "UTF-8"); Manifest sf = new Manifest(); @@ -164,10 +159,10 @@ public class TinySign { } private static void zipAndSha1(File dir, ZipOutputStream zos, DigestOutputStream dos, Manifest m) throws IOException { - File[] arr$ = dir.listFiles(); + File[] files = dir.listFiles(); - if (arr$ != null) { - for (File f : arr$) { + if (files != null) { + for (File f : files) { if (!f.getName().startsWith("META-INF")) { if (f.isFile()) { doFile(f.getName(), f, zos, dos, m); @@ -191,8 +186,8 @@ public class TinySign { public void write(byte[] buffer) throws IOException { try { this.mSignature.update(buffer); - } catch (SignatureException var3) { - throw new IOException("SignatureException: " + var3); + } catch (SignatureException e) { + throw new IOException("SignatureException: " + e); } this.out.write(buffer); @@ -201,8 +196,8 @@ public class TinySign { public void write(byte[] b, int off, int len) throws IOException { try { this.mSignature.update(b, off, len); - } catch (SignatureException var5) { - throw new IOException("SignatureException: " + var5); + } catch (SignatureException e) { + throw new IOException("SignatureException: " + e); } this.out.write(b, off, len); @@ -211,8 +206,8 @@ public class TinySign { public void write(int b) throws IOException { try { this.mSignature.update((byte) b); - } catch (SignatureException var3) { - throw new IOException("SignatureException: " + var3); + } catch (SignatureException e) { + throw new IOException("SignatureException: " + e); } this.out.write(b); diff --git a/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java b/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java index 23596a87..bc47c0a9 100644 --- a/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java +++ b/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java @@ -6,6 +6,7 @@ import android.graphics.Bitmap; import android.os.Bundle; import org.autojs.autojs.tool.BitmapTool; +import org.autojs.autojs6.R; /** * Created by Stardust on 2017/1/20. @@ -54,7 +55,7 @@ public class Shortcut { public Shortcut iconRes(Intent.ShortcutIconResource icon) { if (mIcon != null) { - throw new IllegalStateException("Cannot set both iconRes and icon"); + throw new IllegalStateException(mContext.getString(R.string.error_set_both_icon_res_and_icon)); } mIconRes = icon; return this; @@ -67,7 +68,7 @@ public class Shortcut { public Shortcut icon(Bitmap icon) { if (mIconRes != null) { - throw new IllegalStateException("Cannot set both iconRes and icon"); + throw new IllegalStateException(mContext.getString(R.string.error_set_both_icon_res_and_icon)); } if (icon.getByteCount() > 1024 * 500) { mIcon = BitmapTool.scaleBitmap(icon, 200, 200); diff --git a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java index 756b9848..11d2d0be 100644 --- a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java +++ b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java @@ -2,6 +2,10 @@ package org.autojs.autojs.model.explorer; import androidx.annotation.NonNull; +import com.stardust.app.GlobalAppContext; + +import org.autojs.autojs6.R; + public class ExplorerChangeEvent { @@ -73,6 +77,6 @@ public class ExplorerChangeEvent { case CHILDREN_CHANGE: return "CHILDREN_CHANGE"; } - throw new IllegalArgumentException("action = " + action); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "action", action)); } } diff --git a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java index 4732e66a..b44b581d 100644 --- a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java +++ b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java @@ -2,11 +2,12 @@ package org.autojs.autojs.model.explorer; import androidx.annotation.NonNull; +import com.stardust.app.GlobalAppContext; import com.stardust.pio.PFile; -import com.stardust.util.ObjectHelper; import com.stardust.util.Objects; import org.autojs.autojs.model.script.ScriptFile; +import org.autojs.autojs6.R; import java.io.File; import java.util.Arrays; @@ -24,7 +25,11 @@ public class ExplorerFileItem implements ExplorerItem { private final ExplorerPage mParent; public ExplorerFileItem(PFile file, ExplorerPage parent) { - ObjectHelper.requireNonNull(file, "file"); + if (file == null) { + throw new NullPointerException(GlobalAppContext + .getString(R.string.error_method_called_with_null_argument, + "ExplorerFileItem.constructor", "file")); + } mFile = file; mParent = parent; } diff --git a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java new file mode 100644 index 00000000..3bc03b43 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java @@ -0,0 +1,597 @@ +package org.autojs.autojs.network; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.text.Html; +import android.text.Spanned; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +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.stardust.app.GlobalAppContext; +import com.stardust.autojs.core.ui.widget.CustomSnackbar; +import com.stardust.util.IntentUtil; + +import org.autojs.autojs.Pref; +import org.autojs.autojs.external.fileprovider.AppFileProvider; +import org.autojs.autojs.network.api.UpdateCheckerApi; +import org.autojs.autojs.network.download.DownloadManager; +import org.autojs.autojs.network.entity.VersionInfo; +import org.autojs.autojs.tool.SimpleObserver; +import org.autojs.autojs6.R; +import org.kohsuke.github.GHAsset; +import org.kohsuke.github.GHRelease; +import org.kohsuke.github.GHRepository; +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.Reader; +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; + +/** + * Created by Stardust on 2017/9/20. + * Modified by SuperMonster003 as of Feb 26, 2022. + */ + +public class UpdateChecker { + + private MaterialDialog mUpdateDialog; + private MaterialDialog mPendingDialog; + + public enum PromptMode {NONE, DIALOG, SNACKBAR} + + private static final String TAG = UpdateChecker.class.getSimpleName(); + 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 GitHub mGitHubConnection; + private UpdateCheckerApi checkerApi; + + private UpdateChecker(Context context, View view, String baseUrl, String url, PromptMode promptMode, SimpleObserver callback) { + mContext = context; + mView = view; + mBaseUrl = baseUrl; + mUrl = url; + mPromptMode = promptMode; + mCallback = callback; + } + + public void checkNow() { + mPendingDialog = getPendingDialog(mContext, R.string.text_checking_update); + + if (mPromptMode == PromptMode.DIALOG) { + mPendingDialog.show(); + } + + getCheckerApi() + .checkForUpdates(mUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new SimpleObserver<>() { + @Override + public void onNext(ResponseBody responseBody) { + if (mPendingDialog.isShowing()) { + mPendingDialog.dismiss(); + } + Pref.refreshLastUpdatesCheckedTimestamp(); + + if (mCallback != null) { + mCallback.onNext(responseBody); + return; + } + + try { + Properties prop = new Properties(); + prop.load(responseBody.byteStream()); + + String versionNameKey = mContext.getString(R.string.key_app_property_version_name); + String versionName = prop.getProperty(versionNameKey); + + String versionCodeKey = mContext.getString(R.string.key_app_property_version_code); + int versionCode = Integer.parseInt(prop.getProperty(versionCodeKey)); + + VersionInfo versionInfo = new VersionInfo(versionName, versionCode); + + switch (mPromptMode) { + case DIALOG -> { + if (versionInfo.isNewer()) { + showDialog(versionInfo); + } else { + GlobalAppContext.toast(R.string.text_is_latest_version); + } + } + case SNACKBAR -> { + if (versionInfo.isNewer()) { + showSnackBar(versionInfo); + } + } + default -> throw new IllegalStateException(mContext.getString( + R.string.error_illegal_argument, + "promptMode", mPromptMode)); + } + } catch (Exception e) { + e.printStackTrace(); + GlobalAppContext.toast(mContext.getString(R.string.error_failed_to_parse_version_info)); + } + } + + @Override + public void onError(@NonNull Throwable e) { + e.printStackTrace(); + + if (mPendingDialog.isShowing()) { + mPendingDialog.dismiss(); + } + + if (mCallback != null) { + mCallback.onError(e); + return; + } + + if (mPromptMode == PromptMode.DIALOG) { + new MaterialDialog.Builder(mContext) + .title(R.string.error_check_for_update) + .content(e.toString()) + .positiveText(R.string.text_cancel) + .canceledOnTouchOutside(false) + .build() + .show(); + } + } + }); + } + + private void showDialog(@NonNull VersionInfo versionInfo) { + showDialog(versionInfo, null); + } + + private void showDialog(@NonNull VersionInfo versionInfo, Context context) { + // TODO by SuperMonster003 on May 30, 2022. + // ! A. Updates ignorance (and settings). + // ! B. VersionInfo: *.properties / *.json. + + Context ctx = context != null ? context : mContext; + + String propVersion = versionInfo.getVersionName(); + if (propVersion == null) { + new MaterialDialog.Builder(ctx) + .title(R.string.error_check_for_update) + .content(R.string.error_parse_version_info) + .positiveText(R.string.dialog_button_back) + .build() + .show(); + return; + } + + mUpdateDialog = new MaterialDialog.Builder(ctx) + .title(propVersion) + .content(R.string.text_getting_release_notes) + .neutralText(R.string.dialog_button_ignore_current_update) + .neutralColor(ctx.getColor(R.color.dialog_button_warn)) + .negativeText(R.string.dialog_button_back) + .negativeColor(ctx.getColor(R.color.dialog_button_default)) + .positiveText(R.string.dialog_button_update_now) + .positiveColor(ctx.getColor(R.color.dialog_button_unavailable)) + .autoDismiss(false) + .cancelable(false) + .build(); + + mPendingDialog = getPendingDialog(ctx, R.string.text_preparing); + + MDButton neutralButton = mUpdateDialog.getActionButton(DialogAction.NEUTRAL); + neutralButton.setOnClickListener(v -> { + // TODO by SuperMonster003 on May 30, 2022. + // ! Updates ignorance. + + new MaterialDialog.Builder(ctx) + .title(R.string.text_prompt) + .content(R.string.text_under_development_content) + .positiveText(R.string.dialog_button_back) + .build() + .show(); + }); + + MDButton negativeButton = mUpdateDialog.getActionButton(DialogAction.NEGATIVE); + negativeButton.setOnClickListener(v -> mUpdateDialog.dismiss()); + + MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE); + positiveButton.setOnClickListener(null); + + mUpdateDialog.show(); + + // TODO by SuperMonster003 on May 31, 2022. + // ! Android 7.x (N and N_MR1) compatibility. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + mUpdateDialog.dismiss(); + new MaterialDialog.Builder(ctx) + .title(R.string.error_check_for_update) + .content(R.string.error_sdk_lower_than_o_not_supported_yet) + .positiveText(R.string.text_cancel) + .cancelable(false) + .build() + .show(); + return; + } + + mGitHubExecutor.execute(() -> { + GitHub github = connectToGitHubIfNeeded(); + if (github == null) { + setDialogContent(mUpdateDialog, R.string.error_cannot_connect_to_github); + return; + } + + String userName = ctx.getString(R.string.developer_full_name); + String repoName = ctx.getString(R.string.app_name); + GHRepository repo = getGitHubRepo(github, userName, repoName); + if (repo == null) { + setDialogContent(mUpdateDialog, ctx.getString(R.string.error_invalid_github_repo, repoName)); + return; + } + + GHRelease release = getGitHubRelease(repo); + if (release == null) { + setDialogContent(mUpdateDialog, R.string.error_get_github_latest_release); + return; + } + + String releaseTag = release.getTagName(); + if (!checkReleaseTagMatchesProp(releaseTag, propVersion)) { + setDialogContent(mUpdateDialog, R.string.error_corresponding_github_release_may_not_published); + return; + } + + String rawHtmlContent = getRawHtmlFromRelease(repo, release); + Spanned htmlContent = Html.fromHtml(rawHtmlContent, Html.FROM_HTML_MODE_COMPACT); + if (htmlContent.toString().isEmpty()) { + setDialogContent(mUpdateDialog, R.string.text_empty_release_note); + } else { + setDialogContent(mUpdateDialog, htmlContent); + } + + PagedIterable assets = getGitHubAssets(release); + if (assets == null) { + setDialogContent(mUpdateDialog, R.string.error_empty_github_release_assets); + return; + } + + setDialogUpdateButton(ctx, assets, versionInfo); + }); + } + + @Nullable + private PagedIterable getGitHubAssets(GHRelease release) { + try { + PagedIterable assets = release.listAssets(); + if (assets.toArray().length > 0) { + return assets; + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressLint("CheckResult") + private void download(Context ctx, String downloadUrl, String fileName, VersionInfo versionInfo) { + File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + final String path = new File(downloadDir, fileName).getPath(); + + Consumer onNext = file -> { + mUpdateDialog = null; + IntentUtil.installApkOrToast(ctx, file.getPath(), AppFileProvider.AUTHORITY); + }; + + Consumer onError = e -> { + Log.d(TAG, "onError: printing stack trace"); + e.printStackTrace(); + + String msg = e.getMessage(); + + MaterialDialog d = new MaterialDialog.Builder(ctx) + .title(R.string.text_download_failed) + .content(msg == null ? ctx.getString(R.string.error_unknown) : msg) + .negativeText(R.string.text_cancel) + .negativeColor(ctx.getColor(R.color.dialog_button_default)) + .onNegative((dialog, which) -> { + dialog.dismiss(); + mUpdateDialog = null; + }) + .positiveText("") + .positiveColor(ctx.getColor(R.color.dialog_button_failure)) + .autoDismiss(false) + .cancelable(false) + .build(); + + if (mPendingDialog != null) { + mPendingDialog.dismiss(); + } + + if (mUpdateDialog != null) { + d.getActionButton(DialogAction.NEGATIVE).setText(R.string.dialog_button_quit); + d.getActionButton(DialogAction.NEGATIVE).setTextColor(ctx.getColor(R.color.dialog_button_caution)); + d.getActionButton(DialogAction.POSITIVE).setText(R.string.dialog_button_retry); + d.getActionButton(DialogAction.POSITIVE).setOnClickListener(v -> { + d.dismiss(); + mUpdateDialog.show(); + }); + } + + d.show(); + }; + + DownloadManager.getInstance().downloadWithProgress(ctx, downloadUrl, path, versionInfo) + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(onNext, onError); + } + + 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)); + positiveButton.setOnClickListener(v -> { + if (!mUpdateDialog.isShowing()) { + return; + } + mUpdateDialog.dismiss(); + mPendingDialog.show(); + + mGitHubExecutor.execute(() -> { + String[] abiList = Build.SUPPORTED_ABIS; + String abiBackup = "universal"; + + String url = null; + String urlBackup = null; + String fileName = null; + String fileNameBackup = null; + + try { + assets: + for (GHAsset ghAsset : ghAssets) { + String assetName = ghAsset.getName(); + String assetUrl = ghAsset.getBrowserDownloadUrl(); + for (String abi : abiList) { + String regex = ".*\\b" + abi + "\\b.*"; + if (assetName.matches(regex)) { + url = assetUrl; + fileName = assetName; + break assets; + } + } + if (assetName.contains(abiBackup)) { + urlBackup = assetUrl; + fileNameBackup = assetName; + } + } + } catch (Exception ignore) { + // Ignored. + } + + mPendingDialog.dismiss(); + + if (url == null && urlBackup == null) { + mHandler.post(() -> new MaterialDialog.Builder(ctx) + .content(R.string.error_cannot_get_download_url) + .positiveText(R.string.text_cancel) + .positiveColor(ctx.getColor(R.color.dialog_button_default)) + .canceledOnTouchOutside(false) + .build() + .show()); + return; + } + + if (url != null) { + downloadWithHandler(ctx, url, fileName, versionInfo); + } else { + GlobalAppContext.toast(R.string.text_github_backup_url_used, Toast.LENGTH_LONG); + downloadWithHandler(ctx, urlBackup, fileNameBackup, versionInfo); + } + }); + }); + } + + private void downloadWithHandler(Context ctx, String url, String fileName, VersionInfo versionInfo) { + mHandler.post(() -> download(ctx, url, fileName, versionInfo)); + } + + private String getRawHtmlFromRelease(GHRepository repo, GHRelease release) { + Reader reader; + try { + reader = repo.renderMarkdown(release.getBody(), MarkdownMode.MARKDOWN); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + StringBuilder textBuilder = new StringBuilder(); + try (Reader r = new BufferedReader(reader)) { + int c; + while ((c = r.read()) != -1) { + textBuilder.append((char) c); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return textBuilder.toString(); + } + + private GHRelease getGitHubRelease(GHRepository repo) { + try { + return repo.getLatestRelease(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private GHRepository getGitHubRepo(GitHub github, String userName, String repoName) { + try { + GHUser user = github.getUser(userName); + return user.getRepository(repoName); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private boolean checkReleaseTagMatchesProp(String releaseTag, String propVersion) { + return releaseTag.endsWith(propVersion); + } + + private void setDialogContent(MaterialDialog dialog, String content) { + mHandler.post(() -> dialog.setContent(content)); + } + + private void setDialogContent(MaterialDialog dialog, Spanned content) { + mHandler.post(() -> dialog.setContent(content)); + } + + private void setDialogContent(MaterialDialog dialog, int resId) { + mHandler.post(() -> dialog.setContent(resId)); + } + + private GitHub connectToGitHubIfNeeded() { + try { + if (mGitHubConnection == null) { + // FIXME by SuperMonster003 on May 31, 2022. + // ! Given that java.time package was added only in Android O (API 26), + // ! API 24 and 25 will throw an java.lang.NoClassDefFoundError here. + mGitHubConnection = GitHub.connectAnonymously(); + } + return mGitHubConnection; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void showSnackBar(@NonNull VersionInfo versionInfo) { + String content = mContext.getString(R.string.text_new_version_found) + ": " + versionInfo.getVersionName(); + + CustomSnackbar snackbar = CustomSnackbar.make(mView, content, BaseTransientBottomBar.LENGTH_INDEFINITE); + snackbar.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setActionOne(R.string.text_updates_snack_bar_act_view, v -> showDialog(versionInfo, mView.getContext())) + .setActionTwo(R.string.text_updates_snack_bar_act_later, null) + .show(); + } + + private MaterialDialog getPendingDialog(Context context, int content) { + // TODO by SuperMonster003 on May 29, 2022. + // ! Add a "CANCEL" button for interruption. + // ! Concurrent programming may be needed. + + return new MaterialDialog.Builder(context) + .content(content) + .autoDismiss(false) + .cancelable(false) + .build(); + } + + public static class Builder { + private final Context mContext; + + private View mView; + private String mBaseUrl; + private String mUrl; + private PromptMode mPromptMode = PromptMode.NONE; + private SimpleObserver mCallback; + + public Builder() { + this(GlobalAppContext.get()); + } + + public Builder(Context context) { + mContext = context; + } + + public Builder(View view) { + this(); + 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; + } + + public Builder setCallback(SimpleObserver callback) { + this.mCallback = callback; + return this; + } + + public UpdateChecker build() { + ensureUrl(); + return new UpdateChecker(mContext, mView, mBaseUrl, mUrl, mPromptMode, mCallback); + } + + private void ensureUrl() { + String regexUrl = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; + 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)); + } + } + } + } + + @NonNull + 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; + } + +} diff --git a/app/src/main/java/org/autojs/autojs/network/VersionService.java b/app/src/main/java/org/autojs/autojs/network/VersionService.java deleted file mode 100644 index 695b9186..00000000 --- a/app/src/main/java/org/autojs/autojs/network/VersionService.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.autojs.autojs.network; - -import androidx.annotation.NonNull; - -import com.google.gson.GsonBuilder; -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; - -import org.autojs.autojs.network.api.UpdateCheckApi; -import org.autojs.autojs.network.entity.VersionInfo; - -import io.reactivex.Observable; -import io.reactivex.schedulers.Schedulers; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -/** - * Created by Stardust on 2017/9/20. - * Modified by SuperMonster003 as of Feb 26, 2022. - */ -public class VersionService { - - private static final String baseUrl = "https://raw.githubusercontent.com/"; - - private static Retrofit mRetrofit; - - public static Observable checkForUpdates() { - Retrofit mRetrofit = getRetrofit(); - return mRetrofit.create(UpdateCheckApi.class) - .checkForUpdates() - .subscribeOn(Schedulers.io()); - } - - @NonNull - private static Retrofit getRetrofit() { - if (mRetrofit == null) { - mRetrofit = new Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build(); - } - return mRetrofit; - } - -} diff --git a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java b/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java similarity index 52% rename from app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java rename to app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java index b4cdc760..781d68fd 100644 --- a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java +++ b/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java @@ -1,19 +1,23 @@ package org.autojs.autojs.network.api; -import org.autojs.autojs.network.entity.VersionInfo; +import java.io.InputStream; import io.reactivex.Observable; +import okhttp3.ResponseBody; import retrofit2.http.GET; import retrofit2.http.Headers; +import retrofit2.http.Streaming; +import retrofit2.http.Url; /** * Created by Stardust on 2017/9/20. * Modified by SuperMonster003 as of Feb 28, 2022. */ -public interface UpdateCheckApi { +public interface UpdateCheckerApi { - @GET("/SuperMonster003/AutoJs6/master/project-versions.json") + @Streaming + @GET() @Headers("Cache-Control: no-cache") - Observable checkForUpdates(); + Observable checkForUpdates(@Url String url); } 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 967d1ae6..05ca7062 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 @@ -1,19 +1,25 @@ package org.autojs.autojs.network.download; -import com.stardust.app.GlobalAppContext; - import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.ColorStateList; import android.util.Log; +import android.widget.ProgressBar; +import androidx.annotation.Nullable; + +import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import com.afollestad.materialdialogs.internal.MDButton; +import com.stardust.app.GlobalAppContext; import com.stardust.concurrent.VolatileBox; import com.stardust.pio.PFiles; -import org.autojs.autojs6.R; import org.autojs.autojs.network.api.DownloadApi; +import org.autojs.autojs.network.entity.VersionInfo; import org.autojs.autojs.tool.SimpleObserver; +import org.autojs.autojs.tool.UpdateUtils; +import org.autojs.autojs6.R; import java.io.File; import java.io.FileOutputStream; @@ -21,17 +27,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; +import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; /** * Created by Stardust on 2017/10/20. @@ -40,32 +50,38 @@ public class DownloadManager { private static final String LOG_TAG = "DownloadManager"; private static DownloadManager sInstance; - private static final int RETRY_COUNT = 3; + private final DownloadApi mDownloadApi; private final ConcurrentHashMap> mDownloadStatuses = new ConcurrentHashMap<>(); + private Disposable mDisposable; + public DownloadManager() { Retrofit mRetrofit = new Retrofit.Builder() - .baseUrl("https://www.autojs.org") + .baseUrl(UpdateUtils.BASE_URL) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .client(new OkHttpClient.Builder() - .addInterceptor(chain -> { - Request request = chain.request(); - Response response = chain.proceed(request); - int tryCount = 0; - while (!response.isSuccessful() && tryCount < RETRY_COUNT) { - tryCount++; - response = chain.proceed(request); - } - return response; - }) - .build() - ) + .client(getOkHttpClient()) .build(); + mDownloadApi = mRetrofit.create(DownloadApi.class); } + private OkHttpClient getOkHttpClient() { + Interceptor interceptor = chain -> { + Request request = chain.request(); + Response response = chain.proceed(request); + int tryCount = 0; + while (!response.isSuccessful() && tryCount < RETRY_COUNT) { + tryCount++; + response = chain.proceed(request); + } + return response; + }; + return new OkHttpClient.Builder() + .addInterceptor(interceptor) + .build(); + } public static DownloadManager getInstance() { if (sInstance == null) { @@ -74,6 +90,11 @@ public class DownloadManager { return sInstance; } + public void disposeIfNeeded() { + if (mDisposable != null) { + mDisposable.dispose(); + } + } public static String parseFileNameLocally(String url) { int i = url.lastIndexOf('-'); @@ -89,7 +110,7 @@ public class DownloadManager { @SuppressWarnings("ResultOfMethodCallIgnored") @SuppressLint("CheckResult") - public Observable download(String url, String path) { + public Observable download(String url, String path) { DownloadTask task = new DownloadTask(url, path); mDownloadApi.download(url) .subscribeOn(Schedulers.io()) @@ -98,26 +119,73 @@ public class DownloadManager { } public Observable downloadWithProgress(Context context, String url, String path) { - String fileName = DownloadManager.parseFileNameLocally(url); - return download(url, path, createDownloadProgressDialog(context, url, fileName)); + String content = context.getString(R.string.text_file_name) + ": " + DownloadManager.parseFileNameLocally(url); + return downloadWithProgress(context, url, path, content); } - private MaterialDialog createDownloadProgressDialog(Context context, String url, String fileName) { - return new MaterialDialog.Builder(context) - .progress(false, 100) - .title(fileName) + public Observable downloadWithProgress(Context context, String url, String path, VersionInfo versionInfo) { + return download(context, url, path, createDownloadProgressDialog(context, url, versionInfo, null)); + } + + public Observable downloadWithProgress(Context context, String url, String path, String content) { + return download(context, url, path, createDownloadProgressDialog(context, url, null, content)); + } + + private MaterialDialog createDownloadProgressDialog(Context context, String url, @Nullable VersionInfo versionInfo, @Nullable String content) { + MaterialDialog d = new MaterialDialog.Builder(context) + .title(context.getString(R.string.text_downloading)) + .positiveText(R.string.dialog_button_cancel_download) + .onPositive((dialog, which) -> { + dialog.dismiss(); + DownloadManager.getInstance().cancelDownload(url); + }) + .progress(false, 100, true) .cancelable(false) - .positiveText(R.string.text_cancel_download) - .onPositive((dialog, which) -> DownloadManager.getInstance().cancelDownload(url)) + .autoDismiss(false) .show(); + + String contentText = versionInfo != null ? versionInfo.toString() : content; + if (contentText != null) { + d.setContent(contentText); + } + + d.setProgressNumberFormat(context.getString(R.string.text_half_ellipsis)); + + ProgressBar progressBar = d.getProgressBar(); + progressBar.setProgressTintList(ColorStateList.valueOf(context.getColor(R.color.dialog_progress_download_tint))); + progressBar.setProgressBackgroundTintList(ColorStateList.valueOf(context.getColor(R.color.dialog_progress_download_bg_tint))); + + MDButton positiveButton = d.getActionButton(DialogAction.POSITIVE); + positiveButton.setTextColor(context.getColor(R.color.dialog_progress_download_act_btn)); + + return d; } - private Observable download(String url, String path, MaterialDialog progressDialog) { + private Observable download(Context context, String url, String path, MaterialDialog progressDialog) { PublishSubject subject = PublishSubject.create(); - DownloadManager.getInstance().download(url, path) + DownloadManager downloadMgr = DownloadManager.getInstance(); + downloadMgr.download(url, path) .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(progressDialog::setProgress) + .doOnNext(o -> { + // 10,000 KB (around but less than 10 MB) + int megaThreshold = 10000 * (1 << 10); + if (o.getTotalBytes() > megaThreshold) { + progressDialog.setProgressNumberFormat(String.format(Locale.getDefault(), + context.getString(R.string.format_dialog_progress_number_format_mega_bytes), + o.getReadMegaBytes(), o.getTotalMegaBytes())); + } else { + progressDialog.setProgressNumberFormat(String.format(Locale.getDefault(), + context.getString(R.string.format_dialog_progress_number_format_kilo_bytes), + o.getReadKiloBytes(), o.getTotalKiloBytes())); + } + progressDialog.setProgress(o.getProgress()); + }) .subscribe(new SimpleObserver<>() { + @Override + public void onSubscribe(Disposable disposable) { + mDisposable = disposable; + } + @Override public void onComplete() { progressDialog.dismiss(); @@ -129,6 +197,8 @@ public class DownloadManager { public void onError(Throwable error) { Log.e(LOG_TAG, "Download failed", error); progressDialog.dismiss(); + disposeIfNeeded(); + getOkHttpClient().dispatcher().cancelAll(); subject.onError(error); } }); @@ -142,6 +212,11 @@ public class DownloadManager { } } + public boolean isCancelled(String url) { + VolatileBox status = mDownloadStatuses.get(url); + return status != null && !status.get(); + } + private class DownloadTask { private final String mUrl; @@ -149,24 +224,27 @@ public class DownloadManager { private final VolatileBox mStatus; private InputStream mInputStream; private FileOutputStream mFileOutputStream; - private final PublishSubject mProgress; + private final PublishSubject mProgress; public DownloadTask(String url, String path) { mUrl = url; mPath = path; mStatus = new VolatileBox<>(true); VolatileBox previous = mDownloadStatuses.put(mUrl, mStatus); - if (previous != null) + if (previous != null) { previous.set(false); + } mProgress = PublishSubject.create(); } - private void startImpl(ResponseBody body) throws IOException { + private void startImpl(ResponseBody body) throws Exception { byte[] buffer = new byte[4096]; mFileOutputStream = new FileOutputStream(mPath); mInputStream = body.byteStream(); long total = body.contentLength(); - long read = 0; + + ProgressInfo o = new ProgressInfo(total); + while (true) { if (!mStatus.get()) { onCancel(); @@ -176,52 +254,51 @@ public class DownloadManager { if (len == -1) { break; } - read += len; + o.incrementRead(len); mFileOutputStream.write(buffer, 0, len); - if (total > 0) { - mProgress.onNext((int) (100 * read / total)); + if (o.getTotalBytes() > 0) { + mProgress.onNext(o); } } mProgress.onComplete(); recycle(); } - public void start(ResponseBody body) { - try { - PFiles.ensureDir(mPath); - startImpl(body); - } catch (Exception e) { - mProgress.onError(e); - } + public void start(ResponseBody body) throws Exception { + PFiles.ensureDir(mPath); + startImpl(body); } private void onCancel() { + GlobalAppContext.toast(R.string.text_download_cancelled); recycle(); // TODO: 2017/12/6 notify? } public void recycle() { + // FIXME by SuperMonster003 on May 31, 2022. + // ! Seems like none of the ways below could stop the downloading process. + // ! Even worse, progress may stuck at around 99% and suspend. + // disposeIfNeeded(); + // getOkHttpClient().dispatcher().cancelAll(); + // body.close(); + mDownloadStatuses.remove(mUrl); - if (mInputStream != null) { - try { - mInputStream.close(); - } catch (IOException ignored) { - - } + try { + mInputStream.close(); + } catch (IOException ignored) { + // Ignored. } - if (mFileOutputStream != null) { - try { - mFileOutputStream.close(); - } catch (IOException ignored) { - } + try { + mFileOutputStream.close(); + } catch (IOException ignored) { + // Ignored. } - } - public PublishSubject progress() { + public PublishSubject progress() { return mProgress; } - } } diff --git a/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java b/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java new file mode 100644 index 00000000..ef3ae4f4 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java @@ -0,0 +1,58 @@ +package org.autojs.autojs.network.download; + +/** + * Created by SuperMonster003 on May 30, 2022. + */ + +public class ProgressInfo { + private long mRead = 0; + private long mTotal; + + public ProgressInfo(long contentLength) { + mTotal = contentLength; + } + + public long getTotalBytes() { + return mTotal; + } + + public float getTotalKiloBytes() { + return (float) (mTotal / Math.pow(2, 10)); + } + + public float getTotalMegaBytes() { + return (float) (mTotal / Math.pow(2, 20)); + } + + public void setTotal(long total) { + mTotal = total; + } + + public void incrementTotal(long i) { + mTotal += i; + } + + public long getReadBytes() { + return mRead; + } + + public float getReadKiloBytes() { + return (float) (mRead / Math.pow(2, 10)); + } + + public float getReadMegaBytes() { + return (float) (mRead / Math.pow(2, 20)); + } + + public void setRead(long read) { + mRead = read; + } + + public void incrementRead(long i) { + mRead += i; + } + + public int getProgress() { + return (int) (mRead * 100 / mTotal); + } +} diff --git a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java index 02f11f21..864c2a87 100644 --- a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java +++ b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java @@ -2,27 +2,63 @@ package org.autojs.autojs.network.entity; import androidx.annotation.NonNull; +import com.stardust.app.GlobalAppContext; + import org.autojs.autojs6.BuildConfig; +import org.autojs.autojs6.R; + +import java.text.MessageFormat; /** * Created by Stardust on 2017/9/20. + * Modified by SuperMonster003 as of May 29, 2022. */ public class VersionInfo { - public int appVersionCode; - public String appVersionName; + private String mVersionName; + private int mVersionCode; + + public VersionInfo(@NonNull String propertiesFileRawString) { + String regexVersionName = "VERSION_NAME=.+"; + String regexVersionCode = "VERSION_BUILD=.+"; + for (String string : propertiesFileRawString.split("\n")) { + if (string.matches(regexVersionName)) { + mVersionName = string.split("=")[1]; + } else if (string.matches(regexVersionCode)) { + mVersionCode = Integer.parseInt(string.split("=")[1]); + } + if (mVersionName != null && mVersionCode > 0) { + break; + } + } + } + + public VersionInfo(@NonNull String versionName, int versionCode) { + mVersionName = versionName; + mVersionCode = versionCode; + } public boolean isNewer() { - return appVersionCode > BuildConfig.VERSION_CODE; + return mVersionCode > BuildConfig.VERSION_CODE; + } + + public boolean isNotIgnored() { + // TODO by SuperMonster003 on May 30, 2022. + return true; } @NonNull @Override public String toString() { - return "UpdateInfo{" + - "appVersionCode=" + appVersionCode + - ", appVersionName='" + appVersionName + '\'' + - '}'; + return MessageFormat.format("{0}: {1} ({2})", GlobalAppContext.getString(R.string.text_version), mVersionName, mVersionCode); + } + + public String getVersionName() { + return mVersionName; + } + + public int getVersionCode() { + return mVersionCode; } } diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java index 34a492d3..4fc12342 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java +++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java @@ -21,6 +21,7 @@ import com.stardust.autojs.runtime.api.Device; import com.stardust.util.MapBuilder; import org.autojs.autojs6.BuildConfig; +import org.autojs.autojs6.R; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -167,7 +168,7 @@ abstract public class JsonSocket extends Socket { } else if (value instanceof JsonElement) { data.add(key, (JsonElement) value); } else { - throw new IllegalArgumentException("cannot put value " + value + " into json"); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_put_value_into_json, value)); } } diff --git a/app/src/main/java/org/autojs/autojs/timing/TimedTask.java b/app/src/main/java/org/autojs/autojs/timing/TimedTask.java index 192c035e..0afe490c 100644 --- a/app/src/main/java/org/autojs/autojs/timing/TimedTask.java +++ b/app/src/main/java/org/autojs/autojs/timing/TimedTask.java @@ -6,10 +6,12 @@ import android.content.Intent; import androidx.annotation.NonNull; +import com.stardust.app.GlobalAppContext; import com.stardust.autojs.execution.ExecutionConfig; import org.autojs.autojs.external.ScriptIntents; import org.autojs.autojs.storage.database.BaseModel; +import org.autojs.autojs6.R; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.LocalDateTime; @@ -100,7 +102,7 @@ public class TimedTask extends BaseModel { dayOfWeek++; nextTimeMillis += TimeUnit.DAYS.toMillis(1); } - throw new IllegalStateException("Should not happen! timeFlag = " + mTimeFlag + ", dayOfWeek = " + DateTime.now().getDayOfWeek()); + return -1; } public static long getDayOfWeekTimeFlag(int dayOfWeek) { @@ -127,7 +129,7 @@ public class TimedTask extends BaseModel { return FLAG_FRIDAY; } - throw new IllegalArgumentException("dayOfWeek = " + dayOfWeek); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "dayOfWeek", dayOfWeek)); } public long getMillis() { diff --git a/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java b/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java index 1cb761dc..5d33b2d7 100644 --- a/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java +++ b/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java @@ -8,10 +8,16 @@ import org.autojs.autojs.ui.log.LogActivity_; public class ConsoleTool { - public static void launch() { - LogActivity_.intent(GlobalAppContext.get()) - .flags(Intent.FLAG_ACTIVITY_NEW_TASK) - .start(); + public static boolean launch() { + try { + LogActivity_.intent(GlobalAppContext.get()) + .flags(Intent.FLAG_ACTIVITY_NEW_TASK) + .start(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } } } diff --git a/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java b/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java new file mode 100644 index 00000000..dceb2602 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java @@ -0,0 +1,59 @@ +package org.autojs.autojs.tool; + +import android.content.Context; +import android.view.View; + +import com.stardust.autojs.annotation.ScriptInterface; + +import org.autojs.autojs.network.UpdateChecker; +import org.autojs.autojs.network.UpdateChecker.PromptMode; + +import okhttp3.ResponseBody; + +/** + * Created by SuperMonster003 on May 29, 2022. + */ + +public class UpdateUtils { + + public static String BASE_URL = "https://raw.githubusercontent.com/"; + public static String RELATIVE_URL = "/SuperMonster003/AutoJs6/master/version.properties"; + public static String URL = BASE_URL + RELATIVE_URL.substring(1); + + @ScriptInterface + public static UpdateChecker getDialogChecker(Context context, String url, SimpleObserver callback) { + return getBuilder(context, url, callback) + .setPromptMode(PromptMode.DIALOG) + .build(); + } + + public static UpdateChecker getDialogChecker(Context context) { + return getDialogChecker(context, null, null); + } + + @ScriptInterface + public static UpdateChecker getSnackbarChecker(View view, String url, SimpleObserver callback) { + return getBuilder(view, url, callback) + .setPromptMode(PromptMode.SNACKBAR) + .build(); + } + + public static UpdateChecker getSnackbarChecker(View view) { + return getSnackbarChecker(view, null, null); + } + + private static UpdateChecker.Builder getBuilder(Context context, String url, SimpleObserver callback) { + return new UpdateChecker.Builder(context) + .setBaseUrl(BASE_URL) + .setUrl(url != null ? url : RELATIVE_URL) + .setCallback(callback); + } + + private static UpdateChecker.Builder getBuilder(View view, String url, SimpleObserver callback) { + return new UpdateChecker.Builder(view) + .setBaseUrl(BASE_URL) + .setUrl(url != null ? url : RELATIVE_URL) + .setCallback(callback); + } + +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java index f2216c9c..98ecd0a5 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java @@ -29,12 +29,12 @@ import com.stardust.pio.PFiles; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.ViewById; -import org.autojs.autojs6.R; import org.autojs.autojs.storage.file.TmpScriptFiles; import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder; import org.autojs.autojs.tool.Observers; import org.autojs.autojs.ui.BaseActivity; import org.autojs.autojs.ui.main.MainActivity_; +import org.autojs.autojs6.R; import java.io.File; import java.io.IOException; @@ -158,11 +158,29 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega Log.d(LOG_TAG, "onActionModeStarted: " + mode); Menu menu = mode.getMenu(); MenuItem item = menu.getItem(menu.size() - 1); - menu.add(item.getGroupId(), R.id.action_delete_line, 10000, R.string.text_delete_line); - menu.add(item.getGroupId(), R.id.action_copy_line, 20000, R.string.text_copy_line); + addMenuItem(menu, item.getGroupId(), R.id.action_delete_line, 10000, R.string.text_delete_line, () -> mEditorMenu.deleteLine()); + addMenuItem(menu, item.getGroupId(), R.id.action_copy_line, 20000, R.string.text_copy_line, () -> mEditorMenu.copyLine()); super.onActionModeStarted(mode); } + private void addMenuItem(Menu menu, int groupId, int itemId, int order, int titleRes, Runnable runnable) { + try { + menu.add(groupId, itemId, order, titleRes).setOnMenuItemClickListener(item -> { + try { + runnable.run(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + }); + } catch (Exception e) { + // @Example android.content.res.Resources.NotFoundException + // ! on MIUI devices (maybe more) + e.printStackTrace(); + } + } + @Override public void onSupportActionModeStarted(@NonNull androidx.appcompat.view.ActionMode mode) { Log.d(LOG_TAG, "onSupportActionModeStarted: mode = " + mode); @@ -215,14 +233,17 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega new ThemeColorMaterialDialogBuilder(this) .title(R.string.text_prompt) .content(R.string.edit_exit_without_save_warn) - .positiveText(R.string.text_cancel) - .negativeText(R.string.text_save_and_exit) - .neutralText(R.string.text_exit_directly) - .onNegative((dialog, which) -> { + .neutralText(R.string.text_back) + .neutralColor(getColor(R.color.dialog_button_default)) + .negativeText(R.string.text_exit_directly) + .negativeColor(getColor(R.color.dialog_button_caution)) + .positiveText(R.string.text_save_and_exit) + .positiveColor(getColor(R.color.dialog_button_hint)) + .onNegative((dialog, which) -> finishAndRemoveFromRecents()) + .onPositive((dialog, which) -> { mEditorView.saveFile(); finishAndRemoveFromRecents(); }) - .onNeutral((dialog, which) -> finishAndRemoveFromRecents()) .show(); } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java index 3e33d334..c04b871a 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java @@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.autojs.script.JavaScriptSource; import com.stardust.pio.PFiles; +import org.autojs.autojs.tool.ConsoleTool; import org.autojs.autojs6.R; import org.autojs.autojs.model.indices.AndroidClass; import org.autojs.autojs.model.indices.ClassSearchingItem; @@ -23,7 +24,6 @@ import org.autojs.autojs.ui.project.BuildActivity; import org.autojs.autojs.ui.project.BuildActivity_; import org.autojs.autojs.ui.common.NotAskAgainDialog; import org.autojs.autojs.ui.edit.editor.CodeEditor; -import org.autojs.autojs.ui.log.LogActivity_; import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder; import com.stardust.util.ClipboardUtil; @@ -54,12 +54,10 @@ public class EditorMenu { public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_log) { - showLog(); - return true; + return ConsoleTool.launch(); } if (itemId == R.id.action_force_stop) { - forceStop(); - return true; + return tryDoing(mEditorView::forceStop); } return onEditOptionsSelected(item) || onJumpOptionsSelected(item) @@ -79,8 +77,7 @@ public class EditorMenu { .content(R.string.hint_long_click_run_to_debug) .positiveText(R.string.text_ok) .show(); - mEditorView.debug(); - return true; + return tryDoing(mEditorView::debug); } if (itemId == R.id.action_remove_all_breakpoints) { mEditor.removeAllBreakpoints(); @@ -118,24 +115,20 @@ public class EditorMenu { private boolean onMoreOptionsSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_console) { - showConsole(); - return true; + return tryDoing(mEditorView::showConsole); } if (itemId == R.id.action_import_java_class) { importJavaPackageOrClass(); return true; } if (itemId == R.id.action_editor_text_size) { - mEditorView.selectTextSize(); - return true; + return tryDoing(mEditorView::selectTextSize); } if (itemId == R.id.action_editor_theme) { - mEditorView.selectEditorTheme(); - return true; + return tryDoing(mEditorView::selectEditorTheme); } if (itemId == R.id.action_open_by_other_apps) { - openByOtherApps(); - return true; + return tryDoing(mEditorView::openByOtherApps); } if (itemId == R.id.action_info) { showInfo(); @@ -212,20 +205,19 @@ public class EditorMenu { return true; } if (itemId == R.id.action_copy_line) { - copyLine(); - return true; + return copyLine(); } if (itemId == R.id.action_delete_line) { - deleteLine(); - return true; + return deleteLine(); + } + if (itemId == R.id.action_paste) { + return paste(); } if (itemId == R.id.action_clear) { - mEditor.setText(""); - return true; + return tryDoing(() -> mEditor.setText("")); } if (itemId == R.id.action_beautify) { - beautifyCode(); - return true; + return tryDoing(mEditorView::beautifyCode); } return false; } @@ -255,11 +247,12 @@ public class EditorMenu { } private void showInfo() { - Observable.zip(Observable.just(mEditor.getText()), mEditor.getLineCount(), (text, lineCount) -> { - String size = PFiles.getHumanReadableSize(text.length()); - return String.format(Locale.getDefault(), mContext.getString(R.string.format_editor_info), - text.length(), lineCount, size); - }) + Observable + .zip(Observable.just(mEditor.getText()), mEditor.getLineCount(), (text, lineCount) -> { + String size = PFiles.getHumanReadableSize(text.length()); + return String.format(Locale.getDefault(), mContext.getString(R.string.format_editor_info), + text.length(), lineCount, size); + }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::showInfo); @@ -272,20 +265,21 @@ public class EditorMenu { .show(); } - private void copyLine() { - mEditor.copyLine(); + protected boolean copyLine() { + return tryDoing(mEditor::copyLine); } - - private void deleteLine() { - mEditor.deleteLine(); + protected boolean deleteLine() { + return tryDoing(mEditor::deleteLine); } - private void paste() { - CharSequence clip = getClip(); - if (clip != null) { - mEditor.insert(clip.toString()); - } + protected boolean paste() { + return tryDoing(() -> { + CharSequence clip = getClip(); + if (clip != null) { + mEditor.insert(clip.toString()); + } + }); } @Nullable @@ -309,25 +303,14 @@ public class EditorMenu { Snackbar.make(mEditorView, R.string.text_already_copied_to_clip, Snackbar.LENGTH_SHORT).show(); } - - private void showLog() { - LogActivity_.intent(mContext).start(); - } - - private void showConsole() { - mEditorView.showConsole(); - } - - private void forceStop() { - mEditorView.forceStop(); - } - - private void openByOtherApps() { - mEditorView.openByOtherApps(); - } - - private void beautifyCode() { - mEditorView.beautifyCode(); + private boolean tryDoing(Runnable callable) { + try { + callable.run(); + return true; + } catch (Exception ignore) { + // Ignored. + } + return false; } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java index 8510a2e9..4d21ee41 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java @@ -15,6 +15,8 @@ import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.Scroller; +import org.autojs.autojs6.R; + import java.util.List; @@ -194,40 +196,34 @@ public class HVScrollView extends FrameLayout { @Override public void addView(View child) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - + ensureNotMoreThanOneChild(); super.addView(child); } @Override public void addView(View child, int index) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - + ensureNotMoreThanOneChild(); super.addView(child, index); } @Override public void addView(View child, ViewGroup.LayoutParams params) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - + ensureNotMoreThanOneChild(); super.addView(child, params); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - + ensureNotMoreThanOneChild(); super.addView(child, index, params); } + private void ensureNotMoreThanOneChild() { + if (getChildCount() > 0) { + throw new IllegalStateException(getContext().getString(R.string.error_only_one_child_for_scroll_view)); + } + } + /** * @return Returns true this ScrollView can be scrolled */ diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java index 60cf739e..ad6d63d2 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java @@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicInteger; public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChangedListener { - public static class HighlightTokens { public final int[] colors; @@ -90,7 +89,6 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged updateTokens(s.toString()); } - public void setTheme(Theme theme) { mTheme = theme; } @@ -112,7 +110,6 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged } catch (IOException neverHappen) { throw new UncheckedIOException(neverHappen); } - }); } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java index 5a595d0a..e75edf87 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java @@ -2,6 +2,8 @@ package org.autojs.autojs.ui.edit.toolbar; import android.os.Bundle; import android.os.Handler; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import android.text.TextUtils; @@ -69,7 +71,7 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mEditorView = findEditorView(view); mDebugger = DebuggerSingleton.get(); diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java index c5cbfd9c..2cee6b16 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java @@ -1,6 +1,8 @@ package org.autojs.autojs.ui.edit.toolbar; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.view.View; @@ -17,9 +19,12 @@ public class SearchToolbarFragment extends ToolbarFragment { public static final String ARGUMENT_SHOW_REPLACE_ITEM = "show_replace_item"; @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - boolean showReplaceItem = getArguments().getBoolean(ARGUMENT_SHOW_REPLACE_ITEM, false); + boolean showReplaceItem = false; + if (getArguments() != null) { + showReplaceItem = getArguments().getBoolean(ARGUMENT_SHOW_REPLACE_ITEM, false); + } view.findViewById(R.id.replace).setVisibility(showReplaceItem ? View.VISIBLE : View.GONE); } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java index b0d85575..e222b0e0 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment; import android.view.View; import org.autojs.autojs.ui.edit.EditorView; +import org.autojs.autojs6.R; import java.util.List; @@ -48,7 +49,7 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi view = (View) view.getParent(); } if (!(view instanceof EditorView)) { - throw new IllegalStateException("cannot find EditorView from child: " + view); + throw new IllegalStateException(getString(R.string.error_cannot_find_editor_view_from_child_view, view)); } return (EditorView) view; } diff --git a/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java b/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java index e3336326..3f1389c1 100644 --- a/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java @@ -38,6 +38,7 @@ import com.heinrichreimersoftware.androidissuereporter.model.github.GithubLogin; import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget; import com.heinrichreimersoftware.androidissuereporter.util.ColorUtils; import com.heinrichreimersoftware.androidissuereporter.util.ThemeUtils; +import com.stardust.app.GlobalAppContext; import com.stardust.theme.ThemeColorManager; import org.autojs.autojs6.BuildConfig; @@ -235,7 +236,8 @@ public abstract class AbstractIssueReporterActivity extends BaseActivity { sendBugReport(new GithubLogin(username, password), null); } else { if (TextUtils.isEmpty(token)) - throw new IllegalStateException("You must provide a GitHub API Token."); + throw new IllegalStateException(GlobalAppContext + .getString(org.autojs.autojs6.R.string.error_github_api_token_needed)); String email = null; if (!TextUtils.isEmpty(inputEmail.getText()) && diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java index 68c14487..76fef950 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java @@ -48,9 +48,12 @@ import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.ViewById; +import org.autojs.autojs.Pref; import org.autojs.autojs.autojs.AutoJs; import org.autojs.autojs.external.foreground.MainActivityForegroundService; import org.autojs.autojs.model.explorer.Explorers; +import org.autojs.autojs.network.UpdateChecker; +import org.autojs.autojs.tool.UpdateUtils; import org.autojs.autojs.ui.BaseActivity; import org.autojs.autojs.ui.common.NotAskAgainDialog; import org.autojs.autojs.ui.doc.DocsFragment_; @@ -101,6 +104,7 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega @Override protected void onPostResume() { restartIfNeeded(); + autoCheckForUpdatesIfNeeded(); super.onPostResume(); } @@ -111,6 +115,19 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega } } + private void autoCheckForUpdatesIfNeeded() { + if (Pref.isAutoCheckForUpdatesEnabled()) { + long minCheckedInterval = 2 * 60 * 60 * 1000; // 2 hours + long lastChecked = Pref.getLastUpdatesCheckedTimestamp(); + + if (System.currentTimeMillis() - lastChecked > minCheckedInterval) { + View rootView = findViewById(android.R.id.content); + UpdateChecker checker = UpdateUtils.getSnackbarChecker(rootView); + checker.checkNow(); + } + } + } + public void applyThemeColor() { if (ThemeColor.fromPreferences(PreferenceManager.getDefaultSharedPreferences(this), null) == null) { ThemeColor defaultThemeColor = new ThemeColor( diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java index 0cf72453..4e977b55 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java @@ -189,7 +189,10 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { new DrawerMenuGroup(R.string.text_appearance), new DrawerMenuItem(R.drawable.ic_night_mode, R.string.text_night_mode, R.string.key_night_mode, this::toggleNightMode), - new DrawerMenuItem(R.drawable.ic_personalize, R.string.text_theme_color, this::openThemeColorSettings) + new DrawerMenuItem(R.drawable.ic_personalize, R.string.text_theme_color, this::launchThemeColorSettings), + + new DrawerMenuGroup(R.string.text_about), + new DrawerMenuItem(R.drawable.ic_about, R.string.text_about_app_and_developer, this::launchAboutAppAndDeveloper) ))); mDrawerMenu.setAdapter(mDrawerMenuAdapter); mDrawerMenu.setLayoutManager(new LinearLayoutManager(mContext)); @@ -266,7 +269,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { syncForegroundServiceState(); }) .onPositive((dialog, which) -> MainActivityForegroundService.start(mContext)) - .canceledOnTouchOutside(false) .cancelable(false) .show(); } @@ -307,7 +309,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { syncUsageStatsPermissionState(); }) .onPositive((dialog, which) -> requestAppUsagePermission()) - .canceledOnTouchOutside(false) .cancelable(false) .show(); } @@ -350,10 +351,14 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { return ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)).isIgnoringBatteryOptimizations(mPackageName); } - public void openThemeColorSettings(DrawerMenuItemViewHolder holder) { + public void launchThemeColorSettings(DrawerMenuItemViewHolder holder) { SettingsActivity.selectThemeColor(getActivity()); } + public void launchAboutAppAndDeveloper(DrawerMenuItemViewHolder holder) { + SettingsActivity.launchAboutAppAndDeveloper(requireActivity()); + } + public void toggleNightMode(@NonNull DrawerMenuItemViewHolder holder) { ((BaseActivity) requireActivity()).setNightModeEnabled(holder.getSwitchCompat().isChecked()); } @@ -402,7 +407,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { syncWriteSecuritySettingsState(); }) .onPositive((dialog, which) -> grantWriteSecureSettingsAccess()) - .canceledOnTouchOutside(false) .cancelable(false) .show(); } @@ -430,7 +434,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment { syncProjectMediaAccessState(); }) .onPositive((dialog, which) -> grantProjectMediaAccess()) - .canceledOnTouchOutside(false) .cancelable(false) .show(); } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java index e2b5143d..a691b5c2 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java @@ -3,6 +3,7 @@ package org.autojs.autojs.ui.main.task; import android.content.Context; import com.bignerdranch.expandablerecyclerview.model.Parent; +import com.stardust.app.GlobalAppContext; import com.stardust.autojs.execution.ScriptExecution; import org.autojs.autojs6.R; @@ -69,7 +70,7 @@ public abstract class TaskGroup implements Parent { } else if (task instanceof IntentTask) { mTasks.add(new Task.PendingTask((IntentTask) task)); } else { - throw new IllegalArgumentException("task = " + task); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "task", task)); } return pos; } @@ -100,7 +101,7 @@ public abstract class TaskGroup implements Parent { } else if (task instanceof IntentTask) { ((Task.PendingTask) mTasks.get(i)).setIntentTask((IntentTask) task); } else { - throw new IllegalArgumentException("task = " + task); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "task", task)); } } return i; diff --git a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java index 2e1880b8..688bb4b5 100644 --- a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java @@ -126,7 +126,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa } private void downloadPlugin() { - IntentUtil.browse(this, "https://cdn.jsdelivr.net/gh/SuperMonster002/Hello-Sockpuppet@master/" + + IntentUtil.browse(this, "https://raw.githubusercontent.com/SuperMonster002/Hello-Sockpuppet/master/" + "%5B" + "auto.js" + "%5D" + "%5B" + "apk_builder_plugin_4.1.1_alpha2" + "%5D" + "%5B" + "arm-v7a" + "%5D" + diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java index 302fad0c..44e3d694 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java @@ -11,11 +11,11 @@ import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.ViewById; +import org.autojs.autojs.tool.UpdateUtils; import org.autojs.autojs6.BuildConfig; import org.autojs.autojs6.R; import org.autojs.autojs.ui.BaseActivity; import org.autojs.autojs.ui.error.IssueReporterActivity; -import org.autojs.autojs.ui.update.UpdateCheckDialog; import de.psdev.licensesdialog.LicenseResolver; import de.psdev.licensesdialog.LicensesDialog; @@ -113,8 +113,8 @@ public class AboutActivity extends BaseActivity { @SuppressLint("NonConstantResourceId") @Click(R.id.about_functions_button_update) - void checkForUpdated() { - new UpdateCheckDialog(this).show(); + void checkForUpdates() { + UpdateUtils.getDialogChecker(this).checkNow(); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java new file mode 100644 index 00000000..27eee3cc --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java @@ -0,0 +1,60 @@ +package org.autojs.autojs.ui.settings; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.util.AttributeSet; + +import androidx.preference.PreferenceManager; + +import org.autojs.autojs.Pref; +import org.autojs.autojs6.R; + +/** + * Created by SuperMonster003 on May 31, 2022. + */ + +public class CheckForUpdatesPreference extends Preference implements SharedPreferences.OnSharedPreferenceChangeListener { + public CheckForUpdatesPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public CheckForUpdatesPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + mSharedPreferences.registerOnSharedPreferenceChangeListener(this); + } + + public CheckForUpdatesPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CheckForUpdatesPreference(Context context) { + super(context); + init(); + } + + @Override + protected void onAttachedToActivity() { + setSummaryIfNeeded(); + super.onAttachedToActivity(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + setSummaryIfNeeded(); + } + + private void setSummaryIfNeeded() { + String lastChecked = Pref.getLastUpdatesCheckedTimeString(); + if (lastChecked != null) { + setSummary(getContext().getString(R.string.text_last_updates_checked_time, lastChecked)); + } + } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java index d93c80b0..69c2cbf6 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java @@ -1,6 +1,7 @@ package org.autojs.autojs.ui.settings; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -23,10 +24,10 @@ import com.stardust.util.MapBuilder; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.autojs.autojs.Pref; +import org.autojs.autojs.tool.UpdateUtils; import org.autojs.autojs6.R; import org.autojs.autojs.ui.BaseActivity; import org.autojs.autojs.ui.common.NotAskAgainDialog; -import org.autojs.autojs.ui.update.UpdateCheckDialog; import java.util.ArrayList; import java.util.Collection; @@ -66,70 +67,6 @@ public class SettingsActivity extends BaseActivity { .add(new Pair<>(R.color.theme_color_default, R.string.theme_color_default)) .list(); - public static void selectThemeColor(Context context) { - List colorItems = new ArrayList<>(COLOR_ITEMS.size()); - for (Pair item : COLOR_ITEMS) { - colorItems.add(new ColorSelectActivity.ColorItem(context.getString(item.second), ContextCompat.getColor(context, item.first))); - } - ColorSelectActivity.startColorSelect(context, context.getString(R.string.mt_color_picker_title), colorItems); - } - - @NonNull - private static LinkedHashMap getAvailableLanguages(@NonNull Context context) { - LinkedHashMap map = new LinkedHashMap<>(); - map.put(context.getString(R.string.text_app_language_follow_system), - ((BaseActivity) context)::setLocaleFollowSystem); - map.put(context.getString(R.string.text_app_language_simplified_chinese), - () -> ((BaseActivity) context).updateLocale(Locale.SIMPLIFIED_CHINESE)); - map.put(context.getString(R.string.text_app_language_english), - () -> ((BaseActivity) context).updateLocale(Locale.ENGLISH)); - return map; - } - - public static void selectAppLanguage(Context context) { - LinkedHashMap languagesMap = getAvailableLanguages(context); - Set languagesKey = languagesMap.keySet(); - Collection languagesRunnable = languagesMap.values(); - - new MaterialDialog.Builder(context) - .title(R.string.text_app_language) - .items(languagesKey) - .itemsCallbackSingleChoice(Pref.getAppLanguageIndex(), (dialog, itemView, position, text) -> true) - .positiveText(R.string.text_ok) - .onPositive((dialog, which) -> { - int index = dialog.getSelectedIndex(); - Runnable run = (Runnable) languagesRunnable.toArray()[index]; - if (run != null) { - Pref.setAppLanguageIndex(index); - GlobalAppContext.post(run); - } - dialog.dismiss(); - }) - .negativeText(R.string.text_cancel) - .onNegative((dialog, which) -> dialog.dismiss()) - .neutralText(R.string.text_dialog_button_go_to_settings) - .onNeutral((dialog, which) -> { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClassName("com.android.settings", "com.android.settings.LanguageSettings"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - }) - .autoDismiss(false) - .show(); - - showImperfectHint(context); - } - - private static void showImperfectHint(Context context) { - new NotAskAgainDialog.Builder(context, "SettingsActivity.select_app_language_imperfect_hint") - .title(R.string.text_notice) - .content(R.string.text_imperfect_hint_for_app_language) - .positiveText(R.string.text_ok) - .onPositive((dialog, which) -> dialog.dismiss()) - .autoDismiss(false) - .show(); - } - @AfterViews void setUpUI() { setUpToolbar(); @@ -163,14 +100,28 @@ public class SettingsActivity extends BaseActivity { @Override public void onStart() { super.onStart(); + Activity mActivity = getActivity(); ACTION_MAP = new MapBuilder() - .put(getString(R.string.text_theme_color), () -> selectThemeColor(getActivity())) - .put(getString(R.string.text_check_for_updates), () -> new UpdateCheckDialog(getActivity()).show()) - .put(getString(R.string.text_about_app_and_developer), () -> startActivity(new Intent(getActivity(), AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))) - .put(getString(R.string.text_app_language), () -> selectAppLanguage(getActivity())) + .put(getString(R.string.text_theme_color), () -> selectThemeColor(mActivity)) + .put(getString(R.string.text_about_app_and_developer), () -> launchAboutAppAndDeveloper(mActivity)) + .put(getString(R.string.text_app_language), () -> selectAppLanguage(mActivity)) + .put(getString(R.string.text_check_for_updates), () -> checkForUpdates(mActivity)) + .put(getString(R.string.text_manage_ignored_updates), () -> manageIgnoredUpdates(mActivity)) .build(); } + private void manageIgnoredUpdates(Activity mActivity) { + // TODO by SuperMonster003 on May 31, 2022. + // ! Updates ignorance. + + new MaterialDialog.Builder(mActivity) + .title(R.string.text_prompt) + .content(R.string.text_under_development_content) + .positiveText(R.string.dialog_button_back) + .build() + .show(); + } + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { CharSequence title = preference.getTitle(); if (title != null) { @@ -185,4 +136,76 @@ public class SettingsActivity extends BaseActivity { } + public static void checkForUpdates(Context context) { + UpdateUtils.getDialogChecker(context).checkNow(); + } + + public static void selectThemeColor(Context context) { + List colorItems = new ArrayList<>(COLOR_ITEMS.size()); + for (Pair item : COLOR_ITEMS) { + colorItems.add(new ColorSelectActivity.ColorItem(context.getString(item.second), ContextCompat.getColor(context, item.first))); + } + ColorSelectActivity.startColorSelect(context, context.getString(R.string.mt_color_picker_title), colorItems); + } + + public static void selectAppLanguage(Context context) { + LinkedHashMap languagesMap = getAvailableLanguages(context); + Set languagesKey = languagesMap.keySet(); + Collection languagesRunnable = languagesMap.values(); + + new MaterialDialog.Builder(context) + .title(R.string.text_app_language) + .items(languagesKey) + .itemsCallbackSingleChoice(Pref.getAppLanguageIndex(), (dialog, itemView, position, text) -> true) + .positiveText(R.string.text_ok) + .onPositive((dialog, which) -> { + int index = dialog.getSelectedIndex(); + Runnable run = (Runnable) languagesRunnable.toArray()[index]; + if (run != null) { + Pref.setAppLanguageIndex(index); + GlobalAppContext.post(run); + } + dialog.dismiss(); + }) + .negativeText(R.string.text_cancel) + .onNegative((dialog, which) -> dialog.dismiss()) + .neutralText(R.string.dialog_button_go_to_settings) + .onNeutral((dialog, which) -> { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClassName("com.android.settings", "com.android.settings.LanguageSettings"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + }) + .autoDismiss(false) + .show(); + + showImperfectHint(context); + } + + public static void launchAboutAppAndDeveloper(@NonNull Context context) { + context.startActivity(new Intent(context, AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + @NonNull + private static LinkedHashMap getAvailableLanguages(@NonNull Context context) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(context.getString(R.string.text_app_language_follow_system), + ((BaseActivity) context)::setLocaleFollowSystem); + map.put(context.getString(R.string.text_app_language_simplified_chinese), + () -> ((BaseActivity) context).updateLocale(Locale.SIMPLIFIED_CHINESE)); + map.put(context.getString(R.string.text_app_language_english), + () -> ((BaseActivity) context).updateLocale(Locale.ENGLISH)); + return map; + } + + private static void showImperfectHint(Context context) { + new NotAskAgainDialog.Builder(context, "SettingsActivity.select_app_language_imperfect_hint") + .title(R.string.text_notice) + .content(R.string.text_imperfect_hint_for_app_language) + .positiveText(R.string.text_ok) + .onPositive((dialog, which) -> dialog.dismiss()) + .autoDismiss(false) + .show(); + } + } diff --git a/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java b/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java deleted file mode 100644 index 1d3b3ec5..00000000 --- a/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.autojs.autojs.ui.update; - -import android.content.Context; - -import com.afollestad.materialdialogs.MaterialDialog; - -import org.autojs.autojs6.R; - -/** - * Created by Stardust on 2017/9/20. - */ -public class UpdateCheckDialog { - - private final MaterialDialog mProgress; - private final Context mContext; - - public UpdateCheckDialog(Context context) { - mContext = context; - mProgress = new MaterialDialog.Builder(context) - .title(R.string.text_under_development_title) - .content(R.string.text_under_development_content) - .positiveText(R.string.text_ok) - .build(); -// mProgress = new MaterialDialog.Builder(context) -// .progress(true, 0) -// .content(R.string.text_checking_update) -// .build(); - } - - public void show() { - mProgress.show(); -// VersionService.checkForUpdates() -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe(new SimpleObserver<>() { -// @Override -// public void onNext(@NonNull VersionInfo versionInfo) { -// mProgress.dismiss(); -// if (versionInfo.isNewer()) { -// new UpdateInfoDialogBuilder(mContext, versionInfo).show(); -// } else { -// GlobalAppContext.toast(R.string.text_is_latest_version); -// } -// } -// -// @Override -// public void onError(@NonNull Throwable e) { -// e.printStackTrace(); -// mProgress.dismiss(); -// GlobalAppContext.toast(R.string.text_check_update_error); -// } -// }); - } -} diff --git a/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java b/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java deleted file mode 100644 index c137cef4..00000000 --- a/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.autojs.autojs.ui.update; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import androidx.preference.PreferenceManager; - -import androidx.annotation.NonNull; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.stardust.app.GlobalAppContext; -import com.stardust.util.IntentUtil; - -import org.autojs.autojs.Pref; -import org.autojs.autojs6.R; -import org.autojs.autojs.external.fileprovider.AppFileProvider; -import org.autojs.autojs.network.download.DownloadManager; -import org.autojs.autojs.network.entity.VersionInfo; - -import java.io.File; - -import io.reactivex.android.schedulers.AndroidSchedulers; - -/** - * Created by Stardust on 2017/4/9. - */ -public class UpdateInfoDialogBuilder extends MaterialDialog.Builder { - - private static final String KEY_IGNORED_VERSION_PREFIX = "Ignored version: "; - private final SharedPreferences mSharedPreferences; - private VersionInfo mVersionInfo; - - public UpdateInfoDialogBuilder(@NonNull Context context, VersionInfo info) { - super(context); - mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - updateInfo(info); - } - - public void updateInfo(VersionInfo info) { - mVersionInfo = info; - title(GlobalAppContext.getString(R.string.text_new_version_found)); - content(info.appVersionName + "\n" + info.appVersionCode); - } - - @Override - public MaterialDialog show() { - if (mSharedPreferences.getBoolean(KEY_IGNORED_VERSION_PREFIX + mVersionInfo.appVersionCode, false)) { - return null; - } - return super.show(); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - @SuppressLint("CheckResult") - private void directlyDownload(String downloadUrl) { - final String path = new File(Pref.getScriptDirPath(), "autojs6-latest.apk").getPath(); - DownloadManager.getInstance().downloadWithProgress(getContext(), downloadUrl, path) - .subscribeOn(AndroidSchedulers.mainThread()) - .subscribe(file -> IntentUtil.installApkOrToast(getContext(), file.getPath(), AppFileProvider.AUTHORITY), - error -> { - error.printStackTrace(); - GlobalAppContext.toast(R.string.text_download_failed); - }); - - } - -} diff --git a/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java b/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java index 77d6d304..6ceff975 100644 --- a/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java +++ b/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java @@ -2,9 +2,12 @@ package org.autojs.autojs.ui.viewmodel; import android.content.SharedPreferences; +import com.stardust.app.GlobalAppContext; + import org.autojs.autojs.model.explorer.ExplorerItem; import org.autojs.autojs.model.explorer.ExplorerPage; import org.autojs.autojs.model.explorer.ExplorerSorter; +import org.autojs.autojs6.R; import java.util.ArrayList; import java.util.Comparator; @@ -118,7 +121,7 @@ public class ExplorerItemList { case SORT_TYPE_TYPE: return ExplorerSorter.TYPE; } - throw new IllegalArgumentException("unknown type " + sortType); + throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "sortType", sortType)); } public int groupCount() { diff --git a/app/src/main/res/drawable/ic_about.png b/app/src/main/res/drawable/ic_about.png new file mode 100644 index 00000000..52e3c869 Binary files /dev/null and b/app/src/main/res/drawable/ic_about.png differ diff --git a/app/src/main/res/layout/activity_error_report.xml b/app/src/main/res/layout/activity_error_report.xml index dd5a6065..af74ea5b 100644 --- a/app/src/main/res/layout/activity_error_report.xml +++ b/app/src/main/res/layout/activity_error_report.xml @@ -30,5 +30,5 @@ android:scrollbars="vertical" android:textColor="@android:color/secondary_text_light" android:textSize="16sp" - tools:text="错误"/> + tools:text="@string/text_error"/> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2edac342..4faa970e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -26,7 +26,7 @@ android:layout_height="?attr/actionBarSize" android:theme="@style/ToolBarStyle" android:title="@string/app_name" - app:layout_scrollFlags="scroll|enterAlways" + app:layout_scrollFlags="enterAlways" app:popupTheme="@style/AppTheme.PopupOverlay" /> + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_force_stop" + android:title="@string/text_force_stop" + app:showAsAction="never" /> + android:id="@+id/action_find_or_replace" + android:title="@string/text_find_or_replace" + app:showAsAction="never" /> + android:id="@+id/action_copy_all" + android:title="@string/text_copy_all" + app:showAsAction="never" /> + android:id="@+id/action_copy_line" + android:title="@string/text_copy_line" + app:showAsAction="never" /> + android:id="@+id/action_paste" + android:title="@string/text_paste" + app:showAsAction="never" /> + android:id="@+id/action_delete_line" + android:title="@string/text_delete_line" + app:showAsAction="never" /> + android:id="@+id/action_clear" + android:title="@string/text_clear" + app:showAsAction="never" /> + + + android:title="@string/text_jump" + app:showAsAction="never"> + android:id="@+id/action_jump_to_line" + android:title="@string/text_jump_to_line" + app:showAsAction="never" /> + android:id="@+id/action_jump_to_start" + android:title="@string/text_jump_to_start" + app:showAsAction="never" /> + android:id="@+id/action_jump_to_end" + android:title="@string/text_jump_to_end" + app:showAsAction="never" /> + android:id="@+id/action_jump_to_line_start" + android:title="@string/text_jump_to_line_start" + app:showAsAction="never" /> + android:id="@+id/action_jump_to_line_end" + android:title="@string/text_jump_to_line_end" + app:showAsAction="never" /> + android:title="@string/text_debug" + app:showAsAction="never"> + android:id="@+id/action_breakpoint" + android:title="@string/text_set_breakpoint" + app:showAsAction="never" /> + android:id="@+id/action_launch_debugger" + android:title="@string/text_launch_debugger" + app:showAsAction="never" /> + android:id="@+id/action_remove_all_breakpoints" + android:title="@string/text_remove_all_breakpoints" + app:showAsAction="never" /> + android:id="@+id/action_build_apk" + android:title="@string/text_build_apk" + app:showAsAction="never" /> + android:id="@+id/action_log" + android:title="@string/text_log" + app:showAsAction="never" /> + android:id="@+id/action_console" + android:title="@string/text_console" + app:showAsAction="never" /> + android:id="@+id/action_import_java_class" + android:title="@string/text_find_java_classes" + app:showAsAction="never" /> + android:id="@+id/action_info" + android:title="@string/text_info" + app:showAsAction="never" /> + android:id="@+id/action_editor_text_size" + android:title="@string/text_text_size" + app:showAsAction="never" /> + android:id="@+id/action_editor_theme" + android:title="@string/text_editor_theme" + app:showAsAction="never" /> + android:id="@+id/action_open_by_other_apps" + android:title="@string/text_open_by_other_apps" + app:showAsAction="never" /> diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 1aa48a32..4fb2e2b8 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -77,12 +77,12 @@ Build failed Build succeeded Cancel - Cancel downloading + Cancel Can\'t read file Change working directory - Checking for updates + Checking for updates... Check for updates - Check for updates failed + Checking for updates failed Class / Package name Clear Clear file selection @@ -102,6 +102,7 @@ Copy cmd Copy debugging log Copy line + Paste Copy to clipboard Copy value Create failed @@ -123,7 +124,7 @@ Delete line Details Developer details is under development - System Settings + System Settings Download now Directory Display over other apps @@ -365,7 +366,7 @@ The feature will be ready to use in the next few updates Under development Undo - Update + Updates Usage stats access Provides access to device usage history and statistics which results in currentPackage() a more accurate result Username @@ -406,6 +407,42 @@ Yellow Yellow green An explorer page swipe-to-refresh may be needed + Can\'t set both iconRes and icon + ScrollView can host only one direct child + URL must starts with a protocol like \"https://\" and so forth + Relative URL can\'t be used without base URL + Ignore + @string/text_back + Update + Retrieving release notes... + Can\'t connect to GitHub + Invalid GitHub repository: %s + Can\'t get latest GitHub release + Corresponding GitHub release may have not been published yet + Can\'t parse version information + No release note + Can\'t get downloading URL + No GitHub release assets to download + Backup URL has been used + Unknown error + Downloading + File name + Version + Quit + Retry + Preparing... + Download interrupted + Download cancelled + Manage ignored updates + Check for updates automatically and show a snackbar on homepage + Auto check for updates + Last checked: %s + Failed to parse version info + Can\'t put value %s into JSON + You must provide a GitHub API Token + Cann\'t find EditorView from child view: %s + Unknown type: %s + Current function is not supported by Android SDK versions lower than 26 (Android 8.0) yet The description must have at least %d character The description must have at least %d characters diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 3c5feac9..94d128f6 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -77,12 +77,12 @@ 打包失败 打包成功 取消 - 取消下载 + 取消下载 无法读取文件 更改工作路径 (脚本文件夹) - 正在检查更新 + 正在检查更新... 检查更新 - 检查更新失败 + 检查更新失败 类/包名 清空 清空文件选择 @@ -102,6 +102,7 @@ 复制指令 复制调试信息 复制行 + 粘贴 复制到剪贴板 复制值 创建失败 @@ -123,7 +124,7 @@ 删除行 详情 \"开发者详情\" 正在开发中... - 系统设置 + 系统设置 直接下载 文件夹 显示在其他应用上层 @@ -193,7 +194,7 @@ 无效项目 处理中 问题反馈 - 暂无更新版本 + 已是最新版本 *.js 文件 跳转 跳转到文件末尾 @@ -365,7 +366,7 @@ 将在后续版本实现当前功能 开发中 撤销 - 更新 + 更新 查看使用情况权限 \"查看使用情况\" 权限可获取本设备过去使用应用的情况,从而使 \"获取当前应用\" (currentPackage) 结果更准确 用户名 @@ -406,6 +407,42 @@ 黄色 黄绿 可能需要下拉刷新文件浏览器页面 + 不支持同时设置 iconRes 与 icon + ScrollView 只能拥有至多一个直接子控件 + URL 需以协议标识开头 (如 "https://" 等) + 使用相对 URL 时必须指定 Base URL + 忽略此版本 + @string/text_back + 立即更新 + 正在获取版本信息... + 无法连接到 GitHub + 无效的 GitHub 仓库: %s + 无法获取最新的 GitHub 发行版 + 对应的 GitHub 发行版可能还未发布 + 无法解析版本信息 + 无版本信息 + 无法获取下载链接 + 无可供下载的 GitHub 发行版资源 + 备用 URL 已使用 + 未知错误 + 下载中 + 文件名 + 版本 + 放弃 + 重试 + 准备中... + 下载中断 + 下载已取消 + 管理已忽略更新 + 自动检查更新并在首页下方显示通知条 + 自动检查更新 + 上次检查: %s + 无法解析版本信息 + 无法将值 %s 存入 JSON + 需要提供 GitHub API 令牌 + 无法在子控件 %s 中找到 EditorView + 未知类型: %s + 当前功能暂不支持安卓 SDK 版本低于 26 (Android 8.0) 的系统 描述至少需要 %d 个字 描述至少需要 %d 个字 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5f9faa3b..3625fb2c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -36,6 +36,46 @@ #57606A #2B7A85 #CC7A34 + #ffffff + #03A9F4 + #FF3D00 + #F57C00 + #7B1FA2 + #0DA798 + #A1887F + #BDBDBD + #009624 + @color/dialog_button_finish + #003C8F + @color/dialog_button_error + + #FF6F00 + #FFECB3 + #C43E00 + #F9A825 + #FFF59D + #C17900 + #455A64 + #ECEFF1 + #1C313A + #AB47BC + #F3E5F5 + #790E8B + #00897B + #B2DFDB + #005B4F + #00C853 + #DCEDC8 + #009624 + #00C853 + #DCEDC8 + #009624 + #1565C0 + #BBDEFB + #003C8F + #1565C0 + #BBDEFB + #003C8F diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c1b050c..9b33c452 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,12 +105,12 @@ Build failed Build succeeded Cancel - Cancel downloading + Cancel Can\'t read file Change working directory - Checking for updates + Checking for updates... Check for updates - Check for updates failed + Checking for updates failed Class / Package name Clear Clear file selection @@ -130,6 +130,7 @@ Copy cmd Copy debugging log Copy line + Paste Copy to clipboard Copy value Create failed @@ -151,7 +152,7 @@ Delete line Details Developer details is under development - System Settings + System Settings Download now Directory Display over other apps @@ -400,7 +401,7 @@ The feature will be ready to use in the next few updates Under development Undo - Update + Updates Usage stats access Provides access to device usage history and statistics which results in currentPackage() a more accurate result Username @@ -421,6 +422,47 @@ 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 An explorer page swipe-to-refresh may be needed + Can\'t set both iconRes and icon + ScrollView can host only one direct child + URL must starts with a protocol like \"https://\" and so forth + Relative URL can\'t be used without base URL + Ignore + @string/text_back + Update + Retrieving release notes... + Can\'t connect to GitHub + Invalid GitHub repository: %s + Can\'t get latest GitHub release + Corresponding GitHub release may have not been published yet + Can\'t parse version information + No release note + Can\'t get downloading URL + No GitHub release assets to download + Backup URL has been used + Unknown error + Downloading + File name + Version + Quit + Retry + ... + Preparing... + Download interrupted + Download cancelled + %.2fMB/%.2fMB + %.1fKB/%.1fKB + Manage ignored updates + Auto check for updates + Last checked: %s + VERSION_NAME + VERSION_BUILD + Failed to parse version info + Can\'t put value %s into JSON + You must provide a GitHub API Token + Check for updates automatically and show a snackbar on homepage + Cann\'t find EditorView from child view: %s + Unknown type: %s + Current function is not supported by Android SDK versions lower than 26 (Android 8.0) yet The description must have at least %d character The description must have at least %d characters diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dd5e6635..4adde01c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -79,4 +79,7 @@ + +