From 069932c8d3b8dcb282b979682aa610b6417ea716 Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Tue, 31 May 2022 21:23:36 +0800 Subject: [PATCH] =?UTF-8?q?6.1.1=20-=20=E6=96=B0=E5=A2=9E=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E7=BC=96=E8=BE=91=E5=99=A8=E9=A1=B5=E9=9D=A2/?= =?UTF-8?q?=E5=AE=89=E5=8D=9310=E5=AD=98=E5=82=A8=E6=9D=83=E9=99=90=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=B6=88=E6=81=AF=E8=B5=84=E6=BA=90=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 38 +- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 18 +- app/src/main/assets/doc/CHANGELOG.md | 26 +- .../assets/sample/工具/摩斯电码 [v6.1.0+].js | 2 +- .../ThemeColorPreferenceCategory.java | 5 +- app/src/main/java/org/autojs/autojs/Pref.java | 28 + .../org/autojs/autojs/build/TinySign.java | 33 +- .../autojs/external/shortcut/Shortcut.java | 5 +- .../model/explorer/ExplorerChangeEvent.java | 6 +- .../model/explorer/ExplorerFileItem.java | 9 +- .../autojs/autojs/network/UpdateChecker.java | 597 ++++++++++++++++++ .../autojs/autojs/network/VersionService.java | 45 -- ...ateCheckApi.java => UpdateCheckerApi.java} | 12 +- .../network/download/DownloadManager.java | 195 ++++-- .../autojs/network/download/ProgressInfo.java | 58 ++ .../autojs/network/entity/VersionInfo.java | 50 +- .../autojs/pluginclient/JsonSocket.java | 3 +- .../org/autojs/autojs/timing/TimedTask.java | 6 +- .../org/autojs/autojs/tool/ConsoleTool.java | 14 +- .../org/autojs/autojs/tool/UpdateUtils.java | 59 ++ .../autojs/autojs/ui/edit/EditActivity.java | 37 +- .../org/autojs/autojs/ui/edit/EditorMenu.java | 97 ++- .../autojs/ui/edit/editor/HVScrollView.java | 28 +- .../ui/edit/editor/JavaScriptHighlighter.java | 3 - .../ui/edit/toolbar/DebugToolbarFragment.java | 4 +- .../edit/toolbar/SearchToolbarFragment.java | 9 +- .../ui/edit/toolbar/ToolbarFragment.java | 3 +- .../error/AbstractIssueReporterActivity.java | 4 +- .../autojs/autojs/ui/main/MainActivity.java | 17 + .../autojs/ui/main/drawer/DrawerFragment.java | 15 +- .../autojs/autojs/ui/main/task/TaskGroup.java | 5 +- .../autojs/ui/project/BuildActivity.java | 2 +- .../autojs/ui/settings/AboutActivity.java | 6 +- .../settings/CheckForUpdatesPreference.java | 60 ++ .../autojs/ui/settings/SettingsActivity.java | 161 +++-- .../autojs/ui/update/UpdateCheckDialog.java | 53 -- .../ui/update/UpdateInfoDialogBuilder.java | 67 -- .../autojs/ui/viewmodel/ExplorerItemList.java | 5 +- app/src/main/res/drawable/ic_about.png | Bin 0 -> 10293 bytes .../main/res/layout/activity_error_report.xml | 2 +- app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/menu/menu_editor.xml | 153 ++--- app/src/main/res/values-en/strings.xml | 47 +- app/src/main/res/values-zh/strings.xml | 49 +- app/src/main/res/values/colors.xml | 40 ++ app/src/main/res/values/strings.xml | 52 +- app/src/main/res/values/styles.xml | 3 + app/src/main/res/xml/preferences.xml | 25 +- .../autojs/core/accessibility/UiSelector.java | 56 +- .../autojs/core/console/GlobalConsole.java | 4 +- .../autojs/core/database/CursorHelper.java | 6 +- .../autojs/core/eventloop/EventEmitter.java | 5 +- .../autojs/core/image/ImageWrapper.java | 6 +- .../core/inputevent/InputEventObserver.java | 5 +- .../autojs/core/internal/Functions.java | 6 +- .../autojs/core/looper/TimerThread.java | 4 +- .../autojs/core/ui/dialog/JsDialog.java | 7 +- .../ui/inflater/DynamicLayoutInflater.java | 2 +- .../inflater/inflaters/ImageViewInflater.java | 14 +- .../core/ui/inflater/util/Dimensions.java | 4 +- .../core/ui/inflater/util/ValueMapper.java | 2 +- .../autojs/core/ui/widget/CustomSnackbar.java | 135 ++++ .../widget/CustomSnackbarContentLayout.java | 81 +++ .../autojs/core/util/ProcessShell.java | 4 +- .../autojs/engine/JavaScriptEngine.java | 4 +- .../stardust/autojs/engine/ScriptEngine.java | 8 +- .../autojs/engine/ScriptEngineFactory.java | 12 +- .../autojs/rhino/AndroidClassLoader.java | 4 +- .../autojs/runtime/ScriptBridges.java | 2 +- .../autojs/runtime/ScriptRuntime.java | 7 +- .../accessibility/AccessibilityConfig.java | 2 +- .../stardust/autojs/runtime/api/AppUtils.java | 16 +- .../stardust/autojs/runtime/api/Device.java | 4 +- .../stardust/autojs/runtime/api/Events.java | 2 +- .../stardust/autojs/runtime/api/Images.java | 50 +- .../stardust/autojs/runtime/api/Sensors.java | 6 +- .../stardust/autojs/runtime/api/Threads.java | 4 +- .../java/org/autojs/autojs/tool/RootTool.java | 4 +- .../src/main/res/layout/snackbar_custom.xml | 48 ++ autojs/src/main/res/values-en/strings.xml | 16 + autojs/src/main/res/values-zh/strings.xml | 16 + autojs/src/main/res/values/dimens.xml | 6 + autojs/src/main/res/values/strings.xml | 16 + common/build.gradle | 2 + .../com/stardust/app/GlobalAppContext.java | 7 +- .../main/java/com/stardust/pio/PFiles.java | 4 +- .../java/com/stardust/util/ObjectHelper.java | 18 - common/src/main/res/values-en/strings.xml | 1 + common/src/main/res/values-zh/strings.xml | 1 + common/src/main/res/values/strings.xml | 2 + inrt/src/main/AndroidManifest.xml | 3 +- version.properties | 8 +- 93 files changed, 2094 insertions(+), 680 deletions(-) create mode 100644 app/src/main/java/org/autojs/autojs/network/UpdateChecker.java delete mode 100644 app/src/main/java/org/autojs/autojs/network/VersionService.java rename app/src/main/java/org/autojs/autojs/network/api/{UpdateCheckApi.java => UpdateCheckerApi.java} (52%) create mode 100644 app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java create mode 100644 app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java delete mode 100644 app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java delete mode 100644 app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java create mode 100644 app/src/main/res/drawable/ic_about.png create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/widget/CustomSnackbar.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/widget/CustomSnackbarContentLayout.java create mode 100644 autojs/src/main/res/layout/snackbar_custom.xml delete mode 100644 common/src/main/java/com/stardust/util/ObjectHelper.java 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 0000000000000000000000000000000000000000..52e3c8691788a542bbd2e7e98967b620f6846933 GIT binary patch literal 10293 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 literal 0 HcmV?d00001 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 @@ + +