Compare commits

..

47 Commits

Author SHA1 Message Date
LaizyBoy
042ba4b0e9 Remove ADsControlView from SettingsActivity layout
The class  was referenced in  but not present in the current module, causing an InflateException at runtime. The tag has been commented out to prevent the crash while preserving the UI layout structure. No other source files were changed.
2026-07-01 14:53:43 +08:00
50a06f028c 修复git管理脚本 2026-07-01 04:54:11 +08:00
2aed435668 修复git管理脚本 2026-07-01 04:19:41 +08:00
506a8da12c Update build metadata and clean up menu handling
- Bump libappbase to 15.20.34 and libaes to 15.20.17
- Increment buildCount in winboll/build.properties and libwinboll/build.properties (0 → 4)
- Update timestamps to current date
- Apply Java 7 source/target level
- Remove unused library activity menu item from MainActivity
2026-07-01 03:25:50 +08:00
1e40883810 修复编译参数配置 2026-07-01 03:17:14 +08:00
ea90877e6b 移除 libwinboll模块 2026-07-01 03:12:18 +08:00
1bec3dc08d Update build artifacts and dependency versions
- Bump  to 15.20.34 and  to 15.20.17 in libwinboll build.gradle (new release of the core libraries).
- Revise build timestamps in libwinboll/build.properties and winbolt/build.properties to current date.
- Increment  from 0 to 4, reflecting four incremental builds made since last snapshot.
2026-07-01 02:10:49 +08:00
ebd9b64eea <winboll>APK 15.20.8 release Publish. 2026-06-24 08:11:15 +08:00
40f8170751 升级 libappbase/libaes 依赖至最新版,移除米盟 SDK 配置
- cc.winboll.studio:libappbase 15.20.25 → 15.20.33
- cc.winboll.studio:libaes    15.20.14 → 15.20.16
- 注释米盟广告 SDK (mimo-ad-sdk) 依赖及相关 packagingOptions 配置
2026-06-24 06:57:34 +08:00
da92eb7dee 添加--no-daemon参数,解决在Termux环境下编译调用java资源环境不同的问题。 2026-06-11 21:16:33 +08:00
07c3c2e967 添加Termux终端编译发布脚本 2026-06-11 20:48:12 +08:00
Studio
4ac78cd63b <winboll>APK 15.20.7 release Publish. 2026-06-03 07:32:48 +08:00
16c3153d95 改进应用创建函数,提高应用调试能力。 2026-06-03 07:30:38 +08:00
3e65cbc326 更新类库 2026-06-03 07:16:44 +08:00
Studio
97f036bf5e <winboll>APK 15.20.6 release Publish. 2026-06-02 20:00:52 +08:00
76d93acdd5 feat(MyTermuxActivity): 创建桌面快捷方式时使用按钮自定义图标
新增 loadButtonIcon() 加载模型 iconPath 对应图片,
API 26+ 用 Icon.createWithBitmap,旧 API 用 EXTRA_SHORTCUT_ICON,
无图标时回退 ic_menu_manage。
2026-06-02 19:57:29 +08:00
7219fd0c87 feat(TermuxButton): 添加按钮自定义图标功能
- TermuxButtonModel 新增 iconPath 字段及 JSON 序列化
- MyTermuxActivity 编辑对话框增加图标导入/预览/清除
- 列表项改为 LinearLayout,图标 MATCH_PARENT 撑满高度,5dp 边距
- 图标文件保存在 getFilesDir()/termux_icons/
- 新增 strings 国际化资源(中/英)
2026-06-02 19:53:16 +08:00
756cf88b55 feat(MyTermuxActivity): 添加桌面快捷方式及指纹验证功能
- 列表项长按菜单新增"创建桌面快捷方式"
- 支持 ShortcutManager(API 26+) 和 INSTALL_SHORTCUT 广播两种方式
- TermuxCommandExecutor 所有命令执行前增加指纹验证
- 自定义AlertDialog显示命令信息(名称蓝色粗体),通过后启动BiometricPrompt
- 列表项名称蓝色粗体显示
- 新增 EXTRA_SESSION_ACTION 确保每次创建新终端会话
- MyTermuxActivity 添加 shortcut 意图处理(onCreate/onNewIntent)
2026-06-02 19:32:01 +08:00
ac8b789bcb fix(Termux): 修复按钮命令未执行及返回值反转问题
- 移除 stdbuf bash 占位,改为执行命令后进入交互终端
- 替换 \n 为 ; 以支持多行命令分段执行
- 修复 executeTerminalCommand 返回值反转逻辑
- MyTermuxActivity列表文字颜色 white→black 适配浅色主题
2026-06-02 18:09:37 +08:00
bac0a957aa refactor: 将 MyTermuxActivity 移至 termux 包并清理空目录 2026-06-02 17:01:16 +08:00
acfd4744f8 更新菜单排列方式 2026-06-02 09:33:59 +08:00
e15c8076de 更新类库 2026-06-02 09:14:33 +08:00
375635436b Merge remote-tracking branch 'origin/projects_keeper_tag' into winboll 2026-06-02 08:57:30 +08:00
qinglong
db804d1897 合并模块AES 同步最新时间标签aes-v15.20.12 2026-06-02 08:55:05 +08:00
qinglong
039c8fcd98 合并模块WinBoLL 同步最新时间标签winboll-v15.20.5 2026-06-02 04:00:01 +08:00
STUDIO
e8c5cefeac <winboll>APK 15.20.5 release Publish. 2026-06-02 03:18:21 +08:00
85fb42ca97 fix: 修复主题切换时 IndexOutOfBoundsException 崩溃
- App.onCreate() 中调用 AESThemeUtil.init() 注入当前应用的
  R.style.* 主题ID列表(按 ThemeType.ordinal() 顺序排列),
  避免 Jitpack AESThemeUtil 内部 ArrayList 为空导致越界崩溃
- PatternLockActivity / SettingsActivity 删除冗余的
  AESThemeUtil.applyAppTheme(this) 调用(父类 BaseWinBoLLActivity
  已在 onCreate 中通过 setThemeStyle() 处理主题设置)
2026-06-02 03:15:35 +08:00
qinglong
ae63d1ec0a 合并模块AES 同步最新时间标签aes-v15.20.11 2026-06-02 03:00:01 +08:00
f99632cbea Merge branch 'winboll' into merge 2026-06-02 02:58:06 +08:00
c8ef451232 Merge remote-tracking branch 'origin/projects_keeper_tag' into merge 2026-06-02 02:57:59 +08:00
92e59bdb9e 更新类库 2026-06-02 02:55:10 +08:00
9ce03ea542 更新类库 2026-06-02 02:31:39 +08:00
qinglong
9e9486b488 合并模块WinBoLL 同步最新时间标签winboll-v15.20.4 2026-06-01 21:00:01 +08:00
STUDIO
b5d4036d6d <winboll>APK 15.20.4 release Publish. 2026-06-01 20:31:36 +08:00
qinglong
4b8967b253 合并模块WinBoLL 同步最新时间标签winboll-v15.20.3 2026-05-31 21:00:02 +08:00
25daecd8b5 <winboll>APK 15.20.3 release Publish. 2026-05-31 20:40:44 +08:00
7b48ca8fee 去掉应用自定义调试逻辑判断。 2026-05-31 20:39:20 +08:00
26f247b409 <winboll>APK 15.20.2 release Publish. 2026-05-31 20:28:41 +08:00
59080de7f3 更新类库,修复工具栏风格设置问题。 2026-05-27 20:33:43 +08:00
c3f84afb62 Merge remote-tracking branch 'origin/projects_keeper_tag' into winboll 2026-05-27 20:27:10 +08:00
qinglong
b1059c3f46 合并模块AES 同步最新时间标签aes-v15.20.10 2026-05-27 20:26:41 +08:00
00220a382d Merge remote-tracking branch 'origin/projects_keeper_tag' into winboll 2026-05-27 20:26:14 +08:00
qinglong
f3d723fbee 合并模块APPBase 同步最新时间标签appbase-v15.20.22 2026-05-27 15:00:01 +08:00
d0e70407f9 修改应用包权限设置更新说明 2026-05-24 11:17:27 +08:00
79eb4e3247 更新类库 2026-05-24 11:08:06 +08:00
71e6f1f03f Merge remote-tracking branch 'origin/projects_keeper_tag' into winboll 2026-05-24 11:00:11 +08:00
qinglong
e3c30ea9a3 合并模块AES 同步最新时间标签aes-v15.20.9 2026-05-24 10:49:26 +08:00
60 changed files with 1471 additions and 875 deletions

View File

@@ -1,5 +1,5 @@
#!/system/bin/sh
## 合并其他项目分支的模块源码到projects-keeper分支。
## 合并其他项目分支的模块源码到projects_keeper分支。
# ====================== 0. 进入目标目录 ======================
TARGET_DIR="/sdcard/AppProjects/Projects_Keeper"
@@ -36,7 +36,7 @@ fi
# ====================== 3. Git 分支检查 ======================
CUR_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
TARGET_BRANCH="projects-keeper"
TARGET_BRANCH="projects_keeper"
if [ "$CUR_BRANCH" != "$TARGET_BRANCH" ]; then
echo "错误:当前不在 $TARGET_BRANCH 分支!"
@@ -72,7 +72,6 @@ libaes
libappbase
libdebugtemp
libgpsrelaysentinel
libwinboll
local.properties-demo
mymessagemanager
positions
@@ -82,13 +81,14 @@ winboll
winboll.properties-demo
)
# ====================== 5. 获取当前目录真实文件列表 ======================
# ====================== 5. 获取当前目录真实文件列表(兼容过滤 . .. ======================
REAL_ITEMS=()
# 使用固定排序ls自动过滤 . 和 ..,不会进入比对数组
while IFS= read -r line; do
if [[ "$line" != "." && "$line" != ".." ]]; then
REAL_ITEMS+=("$line")
fi
done < <(ls -a)
done < <(LC_COLLATE=C ls -a1 --color=none)
# ====================== 6. 差异比对函数 ======================
check_diff() {
@@ -158,7 +158,7 @@ echo -e "## 对象列表结束
## 合并 APP 项目
MERGE_APP_PROJECT_LIST=(
DemoAPP
WinBoLL
)
echo -e "#@@@ 开始合并应用型模块源码 @@@#
## 目标合并对象列表:"
@@ -166,14 +166,13 @@ echo -e "#@@@ 开始合并应用型模块源码 @@@#
for item in "${MERGE_APP_PROJECT_LIST[@]}"; do
echo "正在合并 $item 项目 ..."
item_lower=$(echo "$item" | tr 'A-Z' 'a-z')
git checkout origin/$item_lower $item_lower
git checkout origin/$item_lower $item_lower
git add .
git commit -m "合并 $item 项目"
done
## 合并 LIB 项目
MERGE_LIB_PROJECT_LIST=(
WinBoLL
APPBase
AES
)
@@ -183,10 +182,10 @@ echo -e "#@@@ 开始合并类库型模块源码 @@@#
for item in "${MERGE_LIB_PROJECT_LIST[@]}"; do
echo "正在合并 $item 项目 ..."
item_lower=$(echo "$item" | tr 'A-Z' 'a-z')
git checkout origin/$item_lower $item_lower lib$item_lower
git checkout origin/$item_lower $item_lower lib$item_lower
git add .
git commit -m "合并 $item 项目"
done
echo '正在推送 Projects_Keeper 项目'
git push
git push

View File

@@ -38,7 +38,7 @@ if [ "${NOW_BRANCH}" != "${TARGET_BRANCH}" ];then
exit 1
fi
# 目录结构校验
# 目录结构校验白名单(不含 . ..
MERGE_OBJECTS_LIST=(
.git
.gitignore
@@ -65,7 +65,6 @@ libaes
libappbase
libdebugtemp
libgpsrelaysentinel
libwinboll
local.properties-demo
mymessagemanager
positions
@@ -76,9 +75,13 @@ winboll.properties-demo
)
REAL_ITEMS=()
# 标准排序ls输出循环强制过滤 . 和 ..
while IFS= read -r line; do
[[ $line != "." && $line != ".." ]] && REAL_ITEMS+=("$line")
done < <(ls -a)
# 跳过虚拟目录 . 和 ..
if [[ "$line" != "." && "$line" != ".." ]]; then
REAL_ITEMS+=("$line")
fi
done < <(LC_COLLATE=C ls -a1 --color=none)
check_diff(){
local miss=() extra=()
@@ -89,7 +92,17 @@ check_diff(){
[[ ! " ${MERGE_OBJECTS_LIST[@]} " =~ " ${i} " ]] && extra+=("$i")
done
if [[ ${#miss[@]} -gt 0 || ${#extra[@]} -gt 0 ]];then
echo "========================================"
echo "本地目录结构不一致,终止运行"
if [[ ${#miss[@]} -gt 0 ]]; then
echo -e "\n缺失条目"
for m in "${miss[@]}"; do echo " $m"; done
fi
if [[ ${#extra[@]} -gt 0 ]]; then
echo -e "\n多余条目"
for e in "${extra[@]}"; do echo " $e"; done
fi
echo "========================================"
exit 1
fi
}
@@ -98,7 +111,7 @@ check_diff
echo -e "#@@@ 按时间获取最新标签合并模块源码 @@@#"
# 应用型模块
MERGE_APP_PROJECT_LIST=(DemoAPP)
MERGE_APP_PROJECT_LIST=(WinBoLL)
echo -e "---------- 应用型模块 ----------"
for name in "${MERGE_APP_PROJECT_LIST[@]}";do
low_name=$(echo "$name" | tr 'A-Z' 'a-z')
@@ -120,7 +133,7 @@ for name in "${MERGE_APP_PROJECT_LIST[@]}";do
done
# 类库模块
MERGE_LIB_PROJECT_LIST=(WinBoLL APPBase AES)
MERGE_LIB_PROJECT_LIST=(APPBase AES)
echo -e "---------- 类库模块 ----------"
for name in "${MERGE_LIB_PROJECT_LIST[@]}";do
low_name=$(echo "$name" | tr 'A-Z' 'a-z')

View File

@@ -0,0 +1,228 @@
#!/usr/bin/bash
# ==============================================================================
# WinBoLL 应用发布脚本
# 功能检查Git源码状态 → 编译Stage Release包 → 添加WinBoLL标签 → 提交并推送源码
# 依赖build.properties、app_update_description.txt项目根目录下
# 使用:./script_name.sh <APP_NAME>
# 作者:豆包&ZhanGSKen<zhangsken@qq.com>
# ==============================================================================
# ==================== 常量定义 ====================
# 脚本退出码
EXIT_CODE_SUCCESS=0
EXIT_CODE_ERR_NO_APP_NAME=2
EXIT_CODE_ERR_WORK_DIR=1
EXIT_CODE_ERR_GIT_CHECK=1
EXIT_CODE_ERR_ADD_WINBOLL_TAG=1
# Gradle 任务(正式发布)
GRADLE_TASK_PUBLISH="assembleStageRelease"
# Gradle 任务(调试用,注释备用)
# GRADLE_TASK_DEBUG="assembleBetaDebug"
# aapt2本地覆盖参数
AAPT2_OVERRIDE_ARG="-Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2"
# 禁用Gradle守护进程
GRADLE_NO_DAEMON="--no-daemon"
# ==================== 函数定义 ====================
# 检查Git源码是否已完全提交无未提交变更
# 返回值0=已完全提交1=存在未提交变更
function checkGitSources() {
# 配置Git安全目录解决权限问题
git config --global --add safe.directory "$(pwd)"
# 检查是否有未提交的变更
if [[ -n $(git diff --stat) ]]; then
echo "[ERROR] Git源码存在未提交变更请先提交所有修改"
return 1
fi
echo "[INFO] Git源码检查通过所有变更已提交。"
return 0
}
# 询问是否添加GitHub Workflows标签当前逻辑注释保留扩展能力
# 返回值1=用户选择是0=用户选择否
function askAddWorkflowsTag() {
read -p "是否添加GitHub Workflows标签(Y/n) " answer
if [[ $answer =~ ^[Yy]$ ]]; then
return 1
else
return 0
fi
}
# 添加WinBoLL正式标签
# 参数:$1=应用名称(项目根目录名)
# 返回值0=标签添加成功1=标签已存在/添加失败
function addWinBoLLTag() {
local app_name=$1
local build_prop_path="${app_name}/build.properties"
# 从build.properties中提取publishVersion
local publish_version=$(grep -o "publishVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
if [[ -z ${publish_version} ]]; then
echo "[ERROR] 未从${build_prop_path}中提取到publishVersion配置"
return 1
fi
echo "[INFO] 从${build_prop_path}读取到publishVersion${publish_version}"
# 构造WinBoLL标签格式<APP_NAME>-v<publishVersion>
local tag="${app_name}-v${publish_version}"
echo "[INFO] 准备添加WinBoLL标签${tag}"
# 检查标签是否已存在
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
echo "[ERROR] WinBoLL标签${tag}已存在!"
return 1
fi
# 添加带注释的标签注释来自app_update_description.txt
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
echo "[INFO] WinBoLL标签${tag}添加成功!"
return 0
}
# 添加GitHub Workflows Beta标签当前逻辑注释保留扩展能力
# 参数:$1=应用名称(项目根目录名)
# 返回值0=标签添加成功1=标签已存在/添加失败
function addWorkflowsTag() {
local app_name=$1
local build_prop_path="${app_name}/build.properties"
# 从build.properties中提取baseBetaVersion
local base_beta_version=$(grep -o "baseBetaVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
if [[ -z ${base_beta_version} ]]; then
echo "[ERROR] 未从${build_prop_path}中提取到baseBetaVersion配置"
return 1
fi
echo "[INFO] 从${build_prop_path}读取到baseBetaVersion${base_beta_version}"
# 构造Workflows标签格式<APP_NAME>-v<baseBetaVersion>-beta
local tag="${app_name}-v${base_beta_version}-beta"
echo "[INFO] 准备添加Workflows标签${tag}"
# 检查标签是否已存在
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
echo "[ERROR] Workflows标签${tag}已存在!"
return 1
fi
# 添加带注释的标签注释来自app_update_description.txt
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
echo "[INFO] Workflows标签${tag}添加成功!"
return 0
}
# ==================== 主流程开始 ====================
echo "============================================="
echo " WinBoLL 应用发布脚本"
echo "============================================="
# 1. 检查应用名称参数是否指定
if [ -z "$1" ]; then
echo "[ERROR] 未指定应用名称!使用方式:${0} <APP_NAME>"
exit ${EXIT_CODE_ERR_NO_APP_NAME}
fi
APP_NAME=$1
echo "[INFO] 待发布应用名称:${APP_NAME}"
# 2. 检查并切换到项目根目录确保build.properties存在
echo "[INFO] 当前工作目录:$(pwd)"
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
echo "[WARNING] 当前目录不存在${APP_NAME}/build.properties尝试切换到上级目录..."
cd ..
echo "[INFO] 切换后工作目录:$(pwd)"
fi
# 验证最终工作目录是否正确
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
echo "[ERROR] 工作目录错误!${APP_NAME}/build.properties 文件不存在。"
exit ${EXIT_CODE_ERR_WORK_DIR}
fi
echo "[INFO] 工作目录验证通过:${APP_NAME}/build.properties 存在。"
# 3. 检查Git源码状态
echo "---------------------------------------------"
echo " 步骤1检查Git源码状态"
echo "---------------------------------------------"
checkGitSources
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] Git源码检查失败脚本终止"
exit ${EXIT_CODE_ERR_GIT_CHECK}
fi
# 4. 编译Stage Release版本APK携带aapt2覆盖参数 + --no-daemon
echo "---------------------------------------------"
echo " 步骤2编译Stage Release APK"
echo "---------------------------------------------"
echo "[INFO] 开始执行Gradle任务${GRADLE_TASK_PUBLISH}"
# 调试用(注释正式任务,启用调试任务)
# bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :${APP_NAME}:${GRADLE_TASK_DEBUG}
bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :${APP_NAME}:${GRADLE_TASK_PUBLISH}
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] Gradle编译任务失败"
exit 1
fi
echo "[INFO] Stage Release APK编译成功"
# 5. 添加WinBoLL正式标签
echo "---------------------------------------------"
echo " 步骤3添加WinBoLL标签"
echo "---------------------------------------------"
addWinBoLLTag ${APP_NAME}
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] WinBoLL标签添加失败脚本终止"
exit ${EXIT_CODE_ERR_ADD_WINBOLL_TAG}
fi
# 6. 可选添加GitHub Workflows标签当前逻辑注释保留扩展能力
# echo "---------------------------------------------"
# echo " 步骤4添加Workflows标签可选"
# echo "---------------------------------------------"
# echo "是否添加GitHub Workflows Beta标签(Y/n) "
# askAddWorkflowsTag
# nAskAddWorkflowsTag=$?
# if [[ ${nAskAddWorkflowsTag} -eq 1 ]]; then
# addWorkflowsTag ${APP_NAME}
# if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
# echo "[ERROR] Workflows标签添加失败脚本终止"
# exit 1
# fi
# fi
# 7. 清理更新描述文件
echo "---------------------------------------------"
echo " 步骤5清理更新描述文件"
echo "---------------------------------------------"
echo "" > "${APP_NAME}/app_update_description.txt"
echo "[INFO] 已清空${APP_NAME}/app_update_description.txt"
# 8. 提交并推送源码与标签
echo "---------------------------------------------"
echo " 步骤6提交并推送源码"
echo "---------------------------------------------"
git add .
git commit -m "<${APP_NAME}> 开始新的Stage版本开发。"
echo "[INFO] 源码提交成功,开始推送..."
# 推送源码到远程仓库
git push origin
# 推送标签到远程仓库
git push origin --tags
if [[ $? -eq ${EXIT_CODE_SUCCESS} ]]; then
echo "[INFO] 源码与标签推送成功!"
else
echo "[ERROR] 源码与标签推送失败!"
exit 1
fi
# ==================== 主流程结束 ====================
echo "============================================="
echo " WinBoLL 应用发布完成!"
echo "============================================="
exit ${EXIT_CODE_SUCCESS}

View File

@@ -0,0 +1,20 @@
#!/usr/bin/bash
# aapt2本地覆盖参数
AAPT2_OVERRIDE_ARG="-Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2"
# Gradle禁用守护进程参数
GRADLE_NO_DAEMON="--no-daemon"
# 检查是否指定了将要发布的类库名称
# 使用 `-z` 命令检查变量是否为空
if [ -z "$1" ]; then
echo "No Library name specified : $0"
exit 2
fi
## 正式发布使用
git pull && bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :$1:publishReleasePublicationToWinBoLLReleaseRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1
## 调试使用
#bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :$1:publishSnapshotWinBoLLPublicationToWinBoLLSnapshotRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1

View File

@@ -122,7 +122,6 @@ android {
// 如果正在调试,就拷贝到 WinBoLL 备份管理文件夹
//
if(variant.flavorName == "beta"&&variant.buildType.name == "debug"){
//File outBuildBckDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/${variant.buildType.name}")
File outBuildBckDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}")
// 创建目标路径目录
if(!outBuildBckDir.exists()) {
@@ -130,6 +129,7 @@ android {
println "Output Folder Created.(WinBoLLStudio) : " + outBuildBckDir.getAbsolutePath()
}
if(outBuildBckDir.exists()) {
def targetApkFile = new File(outBuildBckDir, outputFileName)
copy{
from file.outputFile
into outBuildBckDir
@@ -138,6 +138,14 @@ android {
}
println "Output APK (WinBoLLStudio): " + outBuildBckDir.getAbsolutePath() + "/${outputFileName}"
}
// ========== 设置文件权限为775 ==========
if(targetApkFile.exists()){
exec {
commandLine 'chmod', '775', targetApkFile.absolutePath
}
println "Set file permission to 775 : ${targetApkFile.absolutePath}"
}
// 检查编译标志位配置
assert (winbollBuildProps['buildCount'] != null)
assert (winbollBuildProps['libraryProject'] != null)
@@ -160,8 +168,7 @@ android {
assert(libraryProjectBuildPropsFile.exists())
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
println "\n\n>>> Library Project build.properties saved.\n\n";
}
@@ -172,16 +179,12 @@ android {
//
if(variant.flavorName == "stage"&&variant.buildType.name == "release"){
// 发布 APK 文件
//
// 截取版本号的版本字段为短版本名
String szVersionName = "${versionName}"
String[] szlistTemp = szVersionName.split("-")
String szShortVersionName = szlistTemp[0]
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
println "CommonTagAPKName is : " + szCommonTagAPKName
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/tag/")
// 创建目标路径目录
if(!outTagDir.exists()) {
@@ -192,12 +195,10 @@ android {
if(outTagDir.exists()) {
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
if(targetAPK.exists()) {
// 标签版本APK文件已经存在构建拷贝任务停止
assert (!targetAPK.exists())
// 可选择删除并继续输出APK文件
//delete targetAPK
}
// 复制一个备份
// 复制完整版APK
def fullApkFile = new File(outTagDir, outputFileName)
copy{
from file.outputFile
into outTagDir
@@ -206,7 +207,16 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
}
// 复制一个并重命名为短版本名
// 设置权限775。
if(fullApkFile.exists()){
exec {
commandLine 'chmod', '775', fullApkFile.absolutePath
}
println "Set file permission to 775 : ${fullApkFile.absolutePath}"
}
// 复制短版本名APK
def shortApkFile = new File(outTagDir, szCommonTagAPKName)
copy{
from file.outputFile
into outTagDir
@@ -215,6 +225,14 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
}
// 设置权限775。
if(shortApkFile.exists()){
exec {
commandLine 'chmod', '775', shortApkFile.absolutePath
}
println "Set file permission to 775 : ${shortApkFile.absolutePath}"
}
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['publishVersion'] != null)
@@ -239,14 +257,11 @@ android {
fos.close();
if(winbollBuildProps['libraryProject'] != "") {
// 如果应用 build.properties 文件设置了类库模块项目文件名
// 就拷贝一份新的编译标志配置到类库项目文件夹
File libraryProjectBuildPropsFile = new File("$RootProjectDir/" + winbollBuildProps['libraryProject'] + "/build.properties")
assert(winbollBuildPropsFile.exists())
assert(libraryProjectBuildPropsFile.exists())
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
}
@@ -263,17 +278,12 @@ android {
// 如果正在调试发布版就只生成和输出APK文件不处理 Git 仓库提交与更新问题。
//
if(variant.flavorName == "stage"&&variant.buildType.name == "debug"){
// 发布 APK 文件
//
// 截取版本号的版本字段为短版本名
String szVersionName = "${versionName}"
String[] szlistTemp = szVersionName.split("-")
String szShortVersionName = szlistTemp[0]
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
println "CommonTagAPKName is : " + szCommonTagAPKName
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}/")
// 创建目标路径目录
if(!outTagDir.exists()) {
@@ -284,13 +294,11 @@ android {
if(outTagDir.exists()) {
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
if(targetAPK.exists()) {
// 标签版本APK文件已经存在构建拷贝任务停止
println '如果是在调试 Stage 版应用包构建,请删除(注在debug目录)现有的 Stage 应用包('+targetAPK.getAbsolutePath()+')。再编译一次。'
assert (!targetAPK.exists())
// 可选择删除并继续输出APK文件
//delete targetAPK
}
// 复制一个备份
// 复制完整版APK
def debugFullApk = new File(outTagDir, outputFileName)
copy{
from file.outputFile
into outTagDir
@@ -299,7 +307,16 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
}
// 复制一个并重命名为短版本名
// 权限设为775。
if(debugFullApk.exists()){
exec {
commandLine 'chmod', '775', debugFullApk.absolutePath
}
println "Set file permission to 775 : ${debugFullApk.absolutePath}"
}
// 复制短版本名APK
def debugShortApk = new File(outTagDir, szCommonTagAPKName)
copy{
from file.outputFile
into outTagDir
@@ -308,8 +325,13 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
}
//不保存编译标志配置
// 权限设为775
if(debugShortApk.exists()){
exec {
commandLine 'chmod', '775', debugShortApk.absolutePath
}
println "Set file permission to 775 : ${debugShortApk.absolutePath}"
}
}
}
@@ -328,6 +350,13 @@ android {
}
println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}"
}
// 额外输出文件设置775权限
if(apkFile.exists()){
exec {
commandLine 'chmod', '775', apkFile.absolutePath
}
println "Set file permission to 775 : ${apkFile.absolutePath}"
}
}
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 20 20:04:49 HKT 2026
stageCount=9
#Tue Jun 02 08:54:20 HKT 2026
stageCount=13
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.8
publishVersion=15.20.12
buildCount=0
baseBetaVersion=15.20.9
baseBetaVersion=15.20.13

View File

@@ -5,10 +5,11 @@ package cc.winboll.studio.aes;
* @Date 2024/06/13 19:03:58
* @Describe AES应用类
*/
import android.view.Gravity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.ArrayList;
public class App extends GlobalApplication {
@@ -18,6 +19,7 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
AESThemeUtil.init(null);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架

View File

@@ -84,11 +84,12 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.toolbar_main, menu);
// if(App.isDebugging()) {
// getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
// }
return super.onCreateOptionsMenu(menu);
return true;
}
@Override

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 24 09:53:05 HKT 2026
stageCount=22
#Wed May 27 14:51:29 HKT 2026
stageCount=23
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.21
publishVersion=15.20.22
buildCount=0
baseBetaVersion=15.20.22
baseBetaVersion=15.20.23

0
gradlew vendored Normal file → Executable file
View File

View File

@@ -66,9 +66,9 @@ dependencies {
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libappbase:15.20.18'
//api 'cc.winboll.studio:libappbase:15.20.22'
// 备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.3'
api 'com.github.ZhanGSKen:libappbase:appbase-v15.20.22'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 20 20:04:49 HKT 2026
stageCount=9
#Tue Jun 02 08:54:20 HKT 2026
stageCount=13
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.8
publishVersion=15.20.12
buildCount=0
baseBetaVersion=15.20.9
baseBetaVersion=15.20.13

View File

@@ -15,6 +15,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@@ -22,8 +23,10 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import cc.winboll.studio.libaes.DrawerMenuDataAdapter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.models.DrawerMenuBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
@@ -34,8 +37,8 @@ import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import com.baoyz.widget.PullRefreshLayout;
import java.util.ArrayList;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
public abstract class DrawerFragmentActivity extends AppCompatActivity implements IWinBoLLActivity, AdapterView.OnItemClickListener {
@@ -44,7 +47,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
//protected Context mContext;
ActivityType mActivityType;
ActionBarDrawerToggle mActionBarDrawerToggle;
DrawerLayout mDrawerLayout;
@@ -59,13 +61,14 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
public enum ActivityType { Main, Secondary }
protected volatile AESThemeBean.ThemeType mThemeType;
protected ArrayList<DrawerMenuBean> malDrawerMenuItem;
abstract protected ActivityType initActivityType();
//abstract protected View initContentView(LayoutInflater inflater, ViewGroup rootView);
@Override
protected void onCreate(Bundle savedInstanceState) {
// 替换:使用工具类统一应用主题
AESThemeUtil.applyAppCompatTheme(this);
mThemeType = AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
super.onCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
mActivityType = initActivityType();
@@ -78,53 +81,32 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onDestroy() {
WinBoLLActivityManager.getInstance().registeRemove(this);
WinBoLLActivityManager.getInstance().registeRemove(this);
super.onDestroy();
// 修复:释放广告资源,避免内存泄漏
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.releaseAdResources();
}
// 修复:释放广告资源,避免内存泄漏
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.releaseAdResources();
}
}
/*@Override
public Intent getIntent() {
// TODO: Implement this method
return super.getIntent();
}
public Context getContext() {
return this.mContext;
}*/
@Override
public MenuInflater getMenuInflater() {
// TODO: Implement this method
return super.getMenuInflater();
}
/*public void setSubtitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(context);
}*/
@Override
public void recreate() {
super.recreate();
}
/*@Override
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}*/
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
@@ -135,26 +117,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
super.startActivityForResult(intent, requestCode, options);
}
/*@Override
public FragmentManager getSupportFragmentManager() {
return super.getSupportFragmentManager();
}
public void setSubtitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(resId);
}
public void setTitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setTitle(context);
}
public void setTitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setTitle(resId);
}*/
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return super.getSharedPreferences(name, mode);
@@ -162,7 +124,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public Context getApplicationContext() {
// TODO: Implement this method
return super.getApplicationContext();
}
@@ -173,25 +134,27 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
// 替换为 DrawerFragmentActivity 专属点击处理方法
if (AESThemeUtil.onWinBoLLThemeItemSelected(this, item)) {
recreate();
} if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else {
return super.onOptionsItemSelected(item);
}
}
if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else {
return super.onOptionsItemSelected(item);
}
return true;
return true;
}
@Override
protected void onResume() {
super.onResume();
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.resumeADs(DrawerFragmentActivity.this);
}
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.resumeADs(DrawerFragmentActivity.this);
}
}
void initRootView() {
@@ -213,14 +176,13 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
mPullRefreshLayout = findViewById(R.id.activitydrawerfragmentPullRefreshLayout1);
mPullRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//LogUtils.d(TAG, "onRefresh");
reinitDrawerMenuItemList(malDrawerMenuItem);
mDrawerMenuDataAdapter.notifyDataSetChanged();
mPullRefreshLayout.setRefreshing(false);
}
});
@Override
public void onRefresh() {
reinitDrawerMenuItemList(malDrawerMenuItem);
mDrawerMenuDataAdapter.notifyDataSetChanged();
mPullRefreshLayout.setRefreshing(false);
}
});
malDrawerMenuItem = new ArrayList<DrawerMenuBean>();
@@ -236,68 +198,51 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.lib_name, R.string.lib_name) {
@Override
public void onDrawerOpened(View drawerView) {//完全打开时触发
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
mIsDrawerOpened = true;
mIsDrawerOpening = false;
//Toast.makeText(MainActivity.this,"onDrawerOpened",Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerClosed(View drawerView) {//完全关闭时触发
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
mIsDrawerOpened = false;
mIsDrawerClosing = false;
//Toast.makeText(MainActivity.this,"onDrawerClosed",Toast.LENGTH_SHORT).show();
}
/**
* 当抽屉被滑动的时候调用此方法
* slideOffset表示 滑动的幅度0-1
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
}
/**
* 当抽屉滑动状态改变的时候被调用
* 状态值是STATE_IDLE闲置--0, STATE_DRAGGING拖拽的--1, STATE_SETTLING固定--2中之一。
*具体状态可以慢慢调试
*/
@Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
}
};
//设置显示旋转菜单
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//通过下面这句实现toolbar和Drawer的联动如果没有这行代码箭头是不会随着侧滑菜单的开关而变换的或者没有箭头
// 可以尝试一下,不影响正常侧滑
mActionBarDrawerToggle.syncState();
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
//去掉侧滑的默认图标(动画箭头图标),也可以选择不去,
//不去的话把这一行注释掉或者改成true然后把toolbar.setNavigationIcon注释掉就行了
//mActionBarDrawerToggle.setDrawerIndicatorEnabled(false);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIsDrawerOpened || mIsDrawerOpening) {
mIsDrawerClosing = true;
mIsDrawerOpening = false;
mDrawerLayout.closeDrawer(mPullRefreshLayout);
return;
}
if (!mIsDrawerOpened || mIsDrawerClosing) {
mIsDrawerOpening = true;
mIsDrawerClosing = false;
mDrawerLayout.openDrawer(mPullRefreshLayout);
return;
}
}
});
@Override
public void onClick(View v) {
if (mIsDrawerOpened || mIsDrawerOpening) {
mIsDrawerClosing = true;
mIsDrawerOpening = false;
mDrawerLayout.closeDrawer(mPullRefreshLayout);
return;
}
if (!mIsDrawerOpened || mIsDrawerClosing) {
mIsDrawerOpening = true;
mIsDrawerClosing = false;
mDrawerLayout.openDrawer(mPullRefreshLayout);
return;
}
}
});
initDrawerMenuItemList(malDrawerMenuItem);
}
@@ -305,12 +250,11 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
void initSecondaryRootView() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//LogUtils.d(TAG, "onClick " + Integer.toString(v.getId()));
finish();
}
});
@Override
public void onClick(View v) {
finish();
}
});
}
public <T extends Fragment> int removeFragment(T fragment) {
@@ -375,13 +319,13 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mActivityType == ActivityType.Main) {
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单
if (GlobalApplication.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用信息菜单
// 替换为兼容版菜单加载方法
AESThemeUtil.inflateCompatThemeMenu(this, menu);
// 调试工具菜单
if (GlobalApplication.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用信息菜单
getMenuInflater().inflate(R.menu.toolbar_drawerbase, menu);
}
return super.onCreateOptionsMenu(menu);
@@ -392,3 +336,4 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
super.onActivityResult(who, targetFragment, requestCode);
}
}

View File

@@ -10,6 +10,7 @@ import android.util.JsonWriter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
import java.util.ArrayList;
public class AESThemeBean extends BaseBean {
@@ -41,13 +42,28 @@ public class AESThemeBean extends BaseBean {
return name;
}
}
public static void fillThemeStyleIDList(ArrayList<Integer> themeStyleIDList) {
if (themeStyleIDList == null) {
themeStyleIDList = new ArrayList<Integer>();
}
themeStyleIDList.clear();
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.AESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.DepthAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.SkyAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.GoldenAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.BearingAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.MemorAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.TaoAESTheme);
}
// 保存当前主题
int currentThemeStyleID = getThemeStyleID(ThemeType.AES);
public AESThemeBean() {
}
public AESThemeBean(int currentThemeStyleID) {
this.currentThemeStyleID = currentThemeStyleID;
}
@@ -59,7 +75,7 @@ public class AESThemeBean extends BaseBean {
public int getCurrentThemeTypeID() {
return this.currentThemeStyleID;
}
@Override
public String getName() {
return AESThemeBean.class.getName();
@@ -74,8 +90,7 @@ public class AESThemeBean extends BaseBean {
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if(super.initObjectsFromJsonReader(jsonReader, name)) { return true; }
else{
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("currentThemeTypeID")) {
setCurrentThemeTypeID(jsonReader.nextInt());
} else {
@@ -90,7 +105,7 @@ public class AESThemeBean extends BaseBean {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if(!initObjectsFromJsonReader(jsonReader, name)) {
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}

View File

@@ -12,7 +12,6 @@ import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.views.ASupportToolbar;
import cc.winboll.studio.libappbase.LogUtils;
public class TestASupportToolbarActivity extends AppCompatActivity implements IWinBoLLActivity {
@@ -28,11 +27,12 @@ public class TestASupportToolbarActivity extends AppCompatActivity implements IW
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreate() start");
AESThemeUtil.applyAppTheme(this);
// 替换此处:原 applyAppTheme -> 新方法 applyAppCompatTheme
AESThemeUtil.applyAppCompatTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testasupporttoolbar);
LogUtils.d(TAG, "setContentView() done");
@@ -45,3 +45,4 @@ public class TestASupportToolbarActivity extends AppCompatActivity implements IW
LogUtils.d(TAG, "onCreate() end");
}
}

View File

@@ -14,10 +14,11 @@ import cc.winboll.studio.libaes.utils.AESThemeUtil;
public class TestAToolbarActivity extends Activity {
public static final String TAG = "TestAToolbarActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
AESThemeUtil.applyAppTheme(this);
// 原生Activity 使用 applyTheme
AESThemeUtil.applyTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testatoolbar);
Toolbar toolbar = findViewById(R.id.activitytestatoolbarAToolbar1);
@@ -26,3 +27,4 @@ public class TestAToolbarActivity extends Activity {
}
}

View File

@@ -9,203 +9,165 @@ import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import java.util.ArrayList;
public class AESThemeUtil {
public static final String TAG = "AESThemeUtil";
private static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
private static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
// 私有静态集合,外部不可直接修改
private static ArrayList<Integer> themeStyleIDList = new ArrayList<>();
protected volatile AESThemeBean.ThemeType mThemeType;
// 移除无用实例成员 mThemeType工具类不保留实例字段
public static <T extends Context> int getThemeTypeID(T context) {
AESThemeBean bean = AESThemeBean.loadBean(context, AESThemeBean.class);
return bean == null ? AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES): bean.getCurrentThemeTypeID();
/**
* 初始化主题样式ID集合
*/
public static void init(ArrayList<Integer> themeStyleIDList) {
if(themeStyleIDList == null) {
themeStyleIDList = new ArrayList<Integer>();
AESThemeBean.fillThemeStyleIDList(themeStyleIDList);
}
AESThemeUtil.themeStyleIDList.clear();
AESThemeUtil.themeStyleIDList.addAll(themeStyleIDList);
}
public static <T extends Context> void saveThemeStyleID(T context, int nThemeTypeID) {
AESThemeBean bean = new AESThemeBean(nThemeTypeID);
/**
* 获取当前主题样式ID
*/
public static int getThemeTypeID(Context context) {
AESThemeBean bean = AESThemeBean.loadBean(context, AESThemeBean.class);
return bean == null ? getThemeStyleID(AESThemeBean.ThemeType.AES) : bean.getCurrentThemeTypeID();
}
/**
* 保存主题样式ID
*/
public static void saveThemeStyleID(Context context, int themeTypeID) {
AESThemeBean bean = new AESThemeBean(themeTypeID);
AESThemeBean.saveBean(context, bean);
}
public static <T extends Activity> void applyAppTheme(T activity) {
// ====================== 应用主题 - 规范重载 ======================
/**
* 应用当前持久化主题(通用 Activity
*/
public static void applyTheme(Activity activity) {
activity.setTheme(getThemeTypeID(activity));
}
public static <T extends AppCompatActivity> void applyAppCompatTheme(T activity) {
/**
* 应用指定主题(通用 Activity
*/
public static void applyTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(getThemeStyleID(themeType));
}
/**
* 应用当前持久化主题AppCompat 兼容 Activity
*/
public static void applyAppCompatTheme(AppCompatActivity activity) {
activity.setTheme(getThemeTypeID(activity));
}
/*public static <T extends WinBoLLActivity> void applyWinBoLLTheme(T activity) {
activity.setTheme(getThemeTypeID(activity.getApplicationContext()));
}*/
public static <T extends Activity> void applyAppTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
/**
* 应用指定主题AppCompat 兼容 Activity
*/
public static void applyAppCompatTheme(AppCompatActivity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(getThemeStyleID(themeType));
}
public static <T extends AppCompatActivity> void applyAppCompatTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
}
/*public static <T extends WinBoLLActivity> void applyWinBoLLTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
}*/
public static <T extends Activity> void inflateMenu(T activity, Menu menu) {
// ====================== 加载菜单 ======================
/**
* 加载主题菜单(通用 Activity
*/
public static void inflateThemeMenu(Activity activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}
public static <T extends AppCompatActivity> void inflateCompatMenu(T activity, Menu menu) {
/**
* 加载主题菜单AppCompat Activity
*/
public static void inflateCompatThemeMenu(AppCompatActivity activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}
/*public static <T extends WinBoLLActivity> void inflateWinBoLLMenu(T activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}*/
public static <T extends Activity> boolean onAppThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity, nThemeStyleID);
return true;
// ====================== 菜单点击统一核心逻辑(消除重复代码) ======================
/**
* 主题菜单项点击统一处理
* @param context 上下文(用于持久化)
* @param item 点击的菜单项
* @return 是否消费点击事件
*/
public static boolean handleThemeMenuClick(Context context, MenuItem item) {
int themeStyleId;
int itemId = item.getItemId();
if (R.id.item_depththeme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
} else if (R.id.item_skytheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.SKY);
} else if (R.id.item_goldentheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
} else if (R.id.item_bearingtheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.BEARING);
} else if (R.id.item_memortheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
} else if (R.id.item_taotheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.TAO);
} else if (R.id.item_defaulttheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.AES);
} else {
return false;
}
return false;
saveThemeStyleID(context, themeStyleId);
return true;
}
public static <T extends AppCompatActivity> boolean onAppCompatThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity, nThemeStyleID);
return true;
}
return false;
// 对外暴露不同 Activity 类型的入口,内部调用统一核心方法
public static boolean onThemeItemSelected(Activity activity, MenuItem item) {
return handleThemeMenuClick(activity, item);
}
public static <T extends AppCompatActivity> boolean onWinBoLLThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
}
return false;
public static boolean onAppCompatThemeItemSelected(AppCompatActivity activity, MenuItem item) {
return handleThemeMenuClick(activity, item);
}
public static <T extends DrawerFragmentActivity> boolean onWinBoLLThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
}
public static boolean onWinBoLLThemeItemSelected(AppCompatActivity activity, MenuItem item) {
// 使用 Application 上下文保存,避免 Activity 泄漏
return handleThemeMenuClick(activity.getApplicationContext(), item);
}
return false;
public static boolean onWinBoLLThemeItemSelected(DrawerFragmentActivity activity, MenuItem item) {
return handleThemeMenuClick(activity.getApplicationContext(), item);
}
// ====================== 主题类型转换工具 ======================
/**
* 根据枚举获取对应样式ID
*/
public static int getThemeStyleID(AESThemeBean.ThemeType themeType) {
return themeStyleIDList.get(themeType.ordinal());
}
/**
* 根据样式ID反向获取主题枚举
*/
public static AESThemeBean.ThemeType getThemeStyleType(int themeStyleID) {
for (int i = 0; i < themeStyleIDList.size(); i++) {
if (themeStyleIDList.get(i) == themeStyleID) {
return AESThemeBean.ThemeType.values()[i];
}
}
return AESThemeBean.ThemeType.values()[0];
}
}

View File

@@ -3,15 +3,15 @@
<color name="colorTextColor">#FFFFFFFF</color>
<color name="colorPrimary">#FF03AB4E</color>
<color name="colorPrimaryDark">#FF027C39</color>
<color name="colorAccent">#FF3DDC84</color>
<color name="colorPrimaryDark">#FF3DDC84</color>
<color name="colorAccent">#FF027C39</color>
<color name="colorText">#FFFFFB8D</color>
<color name="colorToastFrame">#FFA9A9A9</color>
<color name="colorToastFrame">#FF555555</color>
<color name="colorToastShadow">#FF000000</color>
<color name="colorToastBackgroung">#FFFFFFFF</color>
<color name="colorAToolbarStartColor">#FF7D3F12</color>
<color name="colorAToolbarCenterColor">#FFCC6E2B</color>
<color name="colorAToolbarEndColor">#FFF4B98F</color>
<color name="colorToastBackgroung">#FF3A3A3A</color>
<color name="colorAToolbarStartColor">#FF5A3A1A</color>
<color name="colorAToolbarCenterColor">#FFA05A2A</color>
<color name="colorAToolbarEndColor">#FFD4A07A</color>
<color name="colorACardShadow">@color/colorPrimaryDark</color>
<color name="colorACardFrame">@color/colorPrimary</color>
@@ -24,7 +24,7 @@
<color name="colorOHPCTSSecondaryProgress">@color/colorPrimary</color>
<color name="colorOHPCTSProgress">@color/colorPrimaryDark</color>
<color name="toolbarBackgroundColor">#FF03AB4E</color>
<color name="toolbarBackgroundColor">#FF3DDC84</color>
<color name="toolbarTextColor">#FFFFFFFF</color>
<color name="mainWindowBackgroundColor">#FF2C2C2C</color>
<color name="mainWindowTextColor">#FFFFFFFF</color>

View File

@@ -2,6 +2,9 @@
<resources>
<style name="AESTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">#FF03AB4E</item>
<item name="colorPrimaryDark">#FF3DDC84</item>
<item name="colorAccent">#FF027C39</item>
<item name="themeDebug">@style/DebugActivityTheme</item>
<item name="aboutViewBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="aboutViewTextColor">@color/mainWindowTextColor</item>
@@ -40,52 +43,52 @@
<style name="AESAToolbar">
<item name="attrAToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrAToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">@color/colorPrimary</item>
<item name="attrAToolbarEndColor">@color/colorAccent</item>
<item name="attrAToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrAToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="AESASupportToolbar">
<item name="attrASupportToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrASupportToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">@color/colorPrimary</item>
<item name="attrASupportToolbarEndColor">@color/colorAccent</item>
<item name="attrASupportToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrASupportToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="DepthAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF0065EC</item>
<item name="colorPrimaryDark">#FF004DB4</item>
<item name="colorAccent">#FF4A97FF</item>
<item name="colorPrimaryDark">#FF4A97FF</item>
<item name="colorAccent">#FF004DB4</item>
</style>
<style name="SkyAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF00A6FF</item>
<item name="colorPrimaryDark">#FF007ABB</item>
<item name="colorAccent">#FF84D4FF</item>
<item name="colorPrimaryDark">#FF84D4FF</item>
<item name="colorAccent">#FF007ABB</item>
</style>
<style name="GoldenAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFF0CA11</item>
<item name="colorPrimaryDark">#FFD3AF00</item>
<item name="colorAccent">#FFFFE35C</item>
<item name="colorPrimaryDark">#FFFFE35C</item>
<item name="colorAccent">#FFD3AF00</item>
</style>
<style name="BearingAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF840FFF</item>
<item name="colorPrimaryDark">#FF6900D7</item>
<item name="colorAccent">#FFBA78FF</item>
<item name="colorPrimaryDark">#FFBA78FF</item>
<item name="colorAccent">#FF6900D7</item>
</style>
<style name="MemorAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFFF00F5</item>
<item name="colorPrimaryDark">#FFE500DC</item>
<item name="colorAccent">#FFFF76FA</item>
<item name="colorPrimaryDark">#FFFF76FA</item>
<item name="colorAccent">#FFE500DC</item>
</style>
<style name="TaoAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFACACAC</item>
<item name="colorPrimaryDark">#FF898989</item>
<item name="colorAccent">#FFD8D8D8</item>
<item name="colorPrimary">#FF7E7E7E</item>
<item name="colorPrimaryDark">#FFE2E2E2</item>
<item name="colorAccent">#FF000000</item>
</style>
<style name="NormalDialogStyle" parent="Theme.AppCompat.Dialog">

View File

@@ -2,6 +2,9 @@
<resources>
<style name="AESTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#FF03AB4E</item>
<item name="colorPrimaryDark">#FF027C39</item>
<item name="colorAccent">#FF3DDC84</item>
<item name="themeDebug">@style/DebugActivityTheme</item>
<item name="aboutViewBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="aboutViewTextColor">@color/mainWindowTextColor</item>
@@ -40,16 +43,16 @@
<style name="AESAToolbar">
<item name="attrAToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrAToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">@color/colorPrimary</item>
<item name="attrAToolbarEndColor">@color/colorAccent</item>
<item name="attrAToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrAToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="AESASupportToolbar">
<item name="attrASupportToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrASupportToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">@color/colorPrimary</item>
<item name="attrASupportToolbarEndColor">@color/colorAccent</item>
<item name="attrASupportToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrASupportToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="DepthAESTheme" parent="AESTheme">
@@ -83,9 +86,9 @@
</style>
<style name="TaoAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFACACAC</item>
<item name="colorPrimaryDark">#FF898989</item>
<item name="colorAccent">#FFD8D8D8</item>
<item name="colorPrimary">#FF7E7E7E</item>
<item name="colorPrimaryDark">#FF000000</item>
<item name="colorAccent">#FFE2E2E2</item>
</style>
<style name="NormalDialogStyle" parent="Theme.AppCompat.Light.Dialog">
@@ -109,4 +112,4 @@
<item name="@android:windowExitAnimation">@anim/normal_dialog_exit_corner</item>
</style>
</resources>
</resources>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 24 09:53:05 HKT 2026
stageCount=22
#Wed May 27 14:51:29 HKT 2026
stageCount=23
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.21
publishVersion=15.20.22
buildCount=0
baseBetaVersion=15.20.22
baseBetaVersion=15.20.23

View File

@@ -59,7 +59,7 @@ public class DebugSwitchInfoImageView extends ImageView {
final AlertDialog dialog = new AlertDialog.Builder(getContext()).create();
dialog.setTitle("调试Token");
dialog.setMessage(getDebugToken());
dialog.setCanceledOnTouchOutside(true);
dialog.setCanceledOnTouchOutside(false);
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "复制到剪贴板", (DialogInterface.OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, "重置", (DialogInterface.OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "关闭", (DialogInterface.OnClickListener) null);

View File

@@ -1 +0,0 @@
/build

View File

@@ -1,56 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 26
targetSdkVersion 30
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// Gson
api 'com.google.code.gson:gson:2.8.9'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
// 添加JSch依赖SFTP核心com.jcraft:jsch:0.1.54
api 'com.jcraft:jsch:0.1.54'
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//implementation 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// WinBoLL库 nexus.winboll.cc 地址
//api 'cc.winboll.studio:libappbase:15.20.20'
//api 'cc.winboll.studio:libaes:15.20.8'
// 备用库 jitpack.io 地址
api 'com.github.ZhanGSKen:libappbase:appbase-v15.20.20'
api 'com.github.ZhanGSKen:libaes:aes-v15.20.8'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 20 12:18:55 GMT 2026
stageCount=2
libraryProject=libwinboll
baseVersion=15.20
publishVersion=15.20.1
buildCount=8
baseBetaVersion=15.20.2

View File

@@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.libwinboll" >
<application>
<activity
android:name=".WinBoLLLibraryActivity">
</activity>
</application>
</manifest>

View File

@@ -1,17 +0,0 @@
package cc.winboll.studio.libwinboll;
import android.app.Activity;
import android.os.Bundle;
import cc.winboll.studio.libappbase.ToastUtils;
public class WinBoLLLibraryActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_winbolllibrary);
ToastUtils.show("WinBoLLLibraryActivity onCreate");
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,11 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="cc.winboll.studio.libwinboll.WinBoLLLibraryActivity"/>
</LinearLayout>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Material.Light">
</style>
</resources>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="lib_name">libwinboll</string>
<string name="hello_world">Hello world!</string>
</resources>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light">
</style>
</resources>

View File

@@ -72,7 +72,6 @@
// WinBoLL 项目编译设置
//include ':winboll'
//include ':libwinboll'
//rootProject.name = "winboll"
// RegExpUtils 项目编译设置

View File

@@ -18,10 +18,14 @@ def genVersionName(def versionName){
}
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
// 适配MIUI12
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
defaultConfig {
applicationId "cc.winboll.studio.winboll"
minSdkVersion 26
@@ -36,21 +40,9 @@ android {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
}
}
}
dependencies {
api project(':libwinboll')
api 'com.google.code.gson:gson:2.10.1'
// 下拉控件
@@ -80,7 +72,7 @@ dependencies {
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//implementation 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
@@ -103,14 +95,17 @@ dependencies {
implementation 'com.termux:terminal-emulator:0.118.0'
implementation 'com.termux:terminal-view:0.118.0'
implementation 'com.termux:termux-shared:0.118.0'
// Biometric (指纹识别)
implementation 'androidx.biometric:biometric:1.1.0'
// WinBoLL库 nexus.winboll.cc 地址
//api 'cc.winboll.studio:libappbase:15.20.20'
//api 'cc.winboll.studio:libaes:15.20.8'
api 'cc.winboll.studio:libappbase:15.20.34'
api 'cc.winboll.studio:libaes:15.20.17'
// 备用库 jitpack.io 地址
api 'com.github.ZhanGSKen:libappbase:appbase-v15.20.20'
api 'com.github.ZhanGSKen:libaes:aes-v15.20.8'
//api 'com.github.ZhanGSKen:libappbase:appbase-v15.20.33'
//api 'com.github.ZhanGSKen:libaes:aes-v15.20.16'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 20 12:19:48 GMT 2026
stageCount=2
libraryProject=libwinboll
#Wed Jul 01 14:49:57 CST 2026
stageCount=9
libraryProject=
baseVersion=15.20
publishVersion=15.20.1
buildCount=8
baseBetaVersion=15.20.2
publishVersion=15.20.8
buildCount=16
baseBetaVersion=15.20.9

View File

@@ -11,6 +11,9 @@
<!-- 发送持久广播 -->
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<!-- 创建桌面快捷方式Android 8.0 以下兼容) -->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<!-- 对正在运行的应用重新排序 -->
<uses-permission android:name="android.permission.REORDER_TASKS"/>
@@ -330,9 +333,20 @@
</activity>
<activity android:name="cc.winboll.studio.winboll.applications.MyTermuxActivity"
<activity android:name="cc.winboll.studio.winboll.termux.MyTermuxActivity"
android:label="@string/my_termux_activity"
android:exported="true"/>
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="cc.winboll.studio.winboll.action.EXECUTE_TERMUX_BUTTON"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>

View File

@@ -14,7 +14,6 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
@@ -22,8 +21,12 @@ import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.GlobalCrashActivity;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -33,45 +36,63 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
public class App extends GlobalApplication {
public static final String TAG = "App";
public static final String COMPONENT_EN1 = "cc.winboll.studio.winboll.MainActivityEN1";
public static final String COMPONENT_CN1 = "cc.winboll.studio.winboll.MainActivityCN1";
public static final String COMPONENT_CN2 = "cc.winboll.studio.winboll.MainActivityCN2";
public static final String ACTION_SWITCHTO_EN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1";
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1";
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2";
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.view_toast);
//ToastUtils.setStyle(new WhiteToastStyle());
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
try {
super.onCreate();
ToastUtils.init(this);
WinBoLLActivityManager.init(this);
// 初始化 AES 主题工具注入当前应用命名空间的主题ID列表按 ThemeType.ordinal() 顺序)
ArrayList<Integer> themeStyleList = new ArrayList<Integer>();
themeStyleList.add(R.style.MyAppTheme); // AES(0)
themeStyleList.add(R.style.MyDepthAppTheme); // DEPTH(1)
themeStyleList.add(R.style.MySkyAppTheme); // SKY(2)
themeStyleList.add(R.style.MyGoldenAppTheme); // GOLDEN(3)
themeStyleList.add(R.style.MyBearingAppTheme); // BEARING(4)
themeStyleList.add(R.style.MyMemorAppTheme); // MEMOR(5)
themeStyleList.add(R.style.MyTaoAppTheme); // TAO(6)
AESThemeUtil.init(themeStyleList);
} catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
String stackTraceStr = sw.toString();
CrashHandleNotifyUtils.handleUncaughtException(
this,
getPackageName(),
stackTraceStr,
GlobalCrashActivity.class
);
}
}
@Override
@@ -79,8 +100,8 @@ public class App extends GlobalApplication {
super.onTerminate();
ToastUtils.release();
}
public static void write(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[1024 * 8];

View File

@@ -17,7 +17,7 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.activities.AboutActivity;
import cc.winboll.studio.winboll.activities.SettingsActivity;
import cc.winboll.studio.winboll.applications.MyTermuxActivity;
import cc.winboll.studio.winboll.termux.MyTermuxActivity;
import cc.winboll.studio.winboll.fragments.BrowserFragment;
import cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity;
import java.util.ArrayList;
@@ -37,7 +37,7 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(cc.winboll.studio.winboll.theme.WinBoLLThemeUtil.getThemeTypeID(this));
setTheme(cc.winboll.studio.winboll.theme.WinBoLLThemeUtil.getThemeTypeID(getApplicationContext()));
super.onCreate(savedInstanceState);
initMainHandler();
if (mBrowserFragment == null) {
@@ -150,11 +150,12 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
super.onCreateOptionsMenu(menu);
if (App.isDebugging()) {
getMenuInflater().inflate(R.menu.toolbar_test, menu);
}
return super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return true;
}
@Override
@@ -192,9 +193,7 @@ public class MainActivity extends DrawerFragmentActivity {
} else if (nItemId == R.id.item_termux_env_test) {
Intent intent = new Intent(getApplicationContext(), TermuxEnvTestActivity.class);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
} else if (nItemId == R.id.item_library_activity) {
Intent intent = new Intent(getApplicationContext(), cc.winboll.studio.libwinboll.WinBoLLLibraryActivity.class);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
} else {
return super.onOptionsItemSelected(item);
}

View File

@@ -14,7 +14,6 @@ import android.os.Looper;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.winboll.R;
@@ -57,7 +56,6 @@ public class PatternLockActivity extends BaseWinBoLLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
AESThemeUtil.applyAppTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pattern_lock);

View File

@@ -3,7 +3,6 @@ package cc.winboll.studio.winboll.activities;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.winboll.R;
import android.app.Activity;
@@ -29,7 +28,6 @@ public class SettingsActivity extends BaseWinBoLLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
AESThemeUtil.applyAppTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);

View File

@@ -1,237 +0,0 @@
package cc.winboll.studio.winboll.applications;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.models.TermuxButtonManager;
import cc.winboll.studio.winboll.models.TermuxButtonModel;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
import java.util.ArrayList;
public class MyTermuxActivity extends AppCompatActivity {
public static final String TAG = "MyTermuxActivity";
private Toolbar mToolbar;
private ListView mListView;
private Button mBtnAdd;
private ButtonAdapter mAdapter;
private ArrayList<TermuxButtonModel> mButtonList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_termux);
initToolbar();
initListView();
initAddButton();
refreshList();
}
private void initToolbar() {
mToolbar = findViewById(R.id.toolbar);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
private void initListView() {
mListView = findViewById(R.id.list_termux_buttons);
mButtonList = new ArrayList<TermuxButtonModel>();
mAdapter = new ButtonAdapter();
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TermuxButtonModel model = mButtonList.get(position);
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getExeCommand(), model.getWorkDir());
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
showContextMenu(position);
return true;
}
});
}
private void initAddButton() {
mBtnAdd = findViewById(R.id.btn_add_termux_button);
mBtnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showButtonDialog(-1, null);
}
});
}
private void refreshList() {
mButtonList.clear();
ArrayList<TermuxButtonModel> loaded = TermuxButtonManager.loadButtons(this);
if (loaded != null) {
mButtonList.addAll(loaded);
}
mAdapter.notifyDataSetChanged();
}
private void showContextMenu(final int position) {
final TermuxButtonModel model = mButtonList.get(position);
String[] items = new String[]{
getString(R.string.menu_execute),
getString(R.string.menu_edit),
getString(R.string.menu_delete),
getString(R.string.menu_cancel)
};
new AlertDialog.Builder(this)
.setTitle(model.getButtonName())
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getExeCommand(), model.getWorkDir());
} else if (which == 1) {
showButtonDialog(position, model);
} else if (which == 2) {
showDeleteConfirmDialog(position);
}
}
})
.show();
}
private void showDeleteConfirmDialog(final int position) {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.dialog_delete_title))
.setMessage(getString(R.string.dialog_delete_message) + mButtonList.get(position).getButtonName())
.setPositiveButton(getString(R.string.dialog_confirm), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxButtonManager.deleteButton(MyTermuxActivity.this, mButtonList, position);
refreshList();
Toast.makeText(MyTermuxActivity.this, R.string.toast_deleted, Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
private void showButtonDialog(final int index, final TermuxButtonModel model) {
final boolean isEdit = (model != null);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
final EditText etName = new EditText(this);
etName.setHint(R.string.hint_button_name);
if (model != null) {
etName.setText(model.getButtonName());
}
layout.addView(etName);
final EditText etCommand = new EditText(this);
etCommand.setHint(R.string.hint_exe_command);
if (model != null) {
etCommand.setText(model.getExeCommand());
}
layout.addView(etCommand);
final EditText etWorkDir = new EditText(this);
etWorkDir.setHint(R.string.hint_work_dir);
if (model != null) {
etWorkDir.setText(model.getWorkDir());
}
layout.addView(etWorkDir);
int titleResId = isEdit ? R.string.dialog_edit_title : R.string.dialog_add_title;
new AlertDialog.Builder(this)
.setTitle(titleResId)
.setView(layout)
.setPositiveButton(getString(R.string.dialog_save), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = etName.getText().toString().trim();
String command = etCommand.getText().toString().trim();
String workDir = etWorkDir.getText().toString().trim();
if (name.isEmpty() || command.isEmpty()) {
Toast.makeText(MyTermuxActivity.this, R.string.toast_fields_required, Toast.LENGTH_SHORT).show();
return;
}
TermuxButtonModel newModel = new TermuxButtonModel();
newModel.setButtonName(name);
newModel.setExeCommand(command);
newModel.setWorkDir(workDir);
if (isEdit) {
TermuxButtonManager.updateButton(MyTermuxActivity.this, mButtonList, index, newModel);
} else {
TermuxButtonManager.addButton(MyTermuxActivity.this, mButtonList, newModel);
}
refreshList();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
private class ButtonAdapter extends BaseAdapter {
@Override
public int getCount() {
return mButtonList.size();
}
@Override
public Object getItem(int position) {
return mButtonList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if (convertView == null) {
tv = new TextView(MyTermuxActivity.this);
tv.setPadding(30, 20, 30, 20);
tv.setTextSize(16);
tv.setMinHeight(80);
} else {
tv = (TextView) convertView;
}
TermuxButtonModel model = mButtonList.get(position);
tv.setText(model.getButtonName() + "\n" + model.getExeCommand());
tv.setTextColor(getResources().getColor(android.R.color.white));
return tv;
}
}
}

View File

@@ -6,7 +6,7 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class MainServiceBean extends BaseBean {

View File

@@ -1,7 +1,8 @@
package cc.winboll.studio.winboll.models;
import android.content.Context;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import cc.winboll.studio.winboll.models.TermuxButtonModel;
import java.util.ArrayList;
public class TermuxButtonManager {

View File

@@ -2,7 +2,7 @@ package cc.winboll.studio.winboll.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
@@ -16,6 +16,7 @@ public class TermuxButtonModel extends BaseBean {
String buttonName;
String exeCommand;
String workDir;
String iconPath;
// 已修改isCommit 改为规范过去式命名 isCommitted
boolean isCommitted;
@@ -26,6 +27,7 @@ public class TermuxButtonModel extends BaseBean {
this.buttonName = "";
this.exeCommand = "";
this.workDir = "";
this.iconPath = "";
// 默认初始化
this.isCommitted = false;
this.commitTitle = "";
@@ -56,6 +58,14 @@ public class TermuxButtonModel extends BaseBean {
return workDir;
}
public void setIconPath(String iconPath) {
this.iconPath = iconPath;
}
public String getIconPath() {
return iconPath;
}
// ========== 已修改 对应 isCommitted 完整 Get & Set ==========
public boolean isCommitted() {
return isCommitted;
@@ -92,6 +102,7 @@ public class TermuxButtonModel extends BaseBean {
jsonWriter.name("buttonName").value(getButtonName());
jsonWriter.name("exeCommand").value(getExeCommand());
jsonWriter.name("workDir").value(getWorkDir());
jsonWriter.name("iconPath").value(getIconPath() != null ? getIconPath() : "");
// JSON写入同步修改
jsonWriter.name("isCommitted").value(isCommitted());
@@ -110,6 +121,8 @@ public class TermuxButtonModel extends BaseBean {
setExeCommand(jsonReader.nextString());
} else if (name.equals("workDir")) {
setWorkDir(jsonReader.nextString());
} else if (name.equals("iconPath")) {
setIconPath(jsonReader.nextString());
}
// JSON解析字段同步修改
else if (name.equals("isCommitted")) {

View File

@@ -7,7 +7,7 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class TestDemoBindServiceBean extends BaseBean {

View File

@@ -7,7 +7,7 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class TestDemoServiceBean extends BaseBean {

View File

@@ -6,7 +6,7 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class UserInfoModel extends BaseBean {

View File

@@ -7,9 +7,9 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
import cc.winboll.studio.libappbase.APPModel;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class WinBoLLModel extends BaseBean {

View File

@@ -7,7 +7,7 @@ package cc.winboll.studio.winboll.models;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class WinBoLLNewsBean extends BaseBean {

View File

@@ -8,7 +8,7 @@ package cc.winboll.studio.winboll.sos;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class SOSCenterServiceModel extends BaseBean {

View File

@@ -7,7 +7,7 @@ package cc.winboll.studio.winboll.sos;
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class SOSObject extends BaseBean {

View File

@@ -0,0 +1,554 @@
package cc.winboll.studio.winboll.termux;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.models.TermuxButtonManager;
import cc.winboll.studio.winboll.models.TermuxButtonModel;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
public class MyTermuxActivity extends AppCompatActivity {
public static final String TAG = "MyTermuxActivity";
public static final String EXTRA_BUTTON_NAME = "extra_button_name";
public static final String ACTION_EXECUTE_SHORTCUT =
"cc.winboll.studio.winboll.action.EXECUTE_TERMUX_BUTTON";
private Toolbar mToolbar;
private ListView mListView;
private Button mBtnAdd;
private ButtonAdapter mAdapter;
private ArrayList<TermuxButtonModel> mButtonList;
private static final int REQUEST_PICK_ICON = 100;
private String mDialogIconPath = "";
private ImageView mDialogIconView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_termux);
initToolbar();
initListView();
initAddButton();
refreshList();
handleShortcutIntent();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleShortcutIntent();
}
private void handleShortcutIntent() {
Intent intent = getIntent();
if (intent != null && ACTION_EXECUTE_SHORTCUT.equals(intent.getAction())) {
String buttonName = intent.getStringExtra(EXTRA_BUTTON_NAME);
if (buttonName != null && buttonName.length() > 0) {
TermuxButtonModel model = findButtonByName(buttonName);
if (model != null) {
TermuxCommandExecutor.openTermuxBash(this,
model.getButtonName(), model.getExeCommand(),
model.getWorkDir(), true);
} else {
Toast.makeText(this, R.string.toast_shortcut_not_found,
Toast.LENGTH_SHORT).show();
}
}
}
}
private TermuxButtonModel findButtonByName(String name) {
for (int i = 0; i < mButtonList.size(); i++) {
if (name.equals(mButtonList.get(i).getButtonName())) {
return mButtonList.get(i);
}
}
return null;
}
private void initToolbar() {
mToolbar = findViewById(R.id.toolbar);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
private void initListView() {
mListView = findViewById(R.id.list_termux_buttons);
mButtonList = new ArrayList<TermuxButtonModel>();
mAdapter = new ButtonAdapter();
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TermuxButtonModel model = mButtonList.get(position);
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getButtonName(), model.getExeCommand(),
model.getWorkDir(), true);
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
showContextMenu(position);
return true;
}
});
}
private void initAddButton() {
mBtnAdd = findViewById(R.id.btn_add_termux_button);
mBtnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showButtonDialog(-1, null);
}
});
}
private void refreshList() {
mButtonList.clear();
ArrayList<TermuxButtonModel> loaded = TermuxButtonManager.loadButtons(this);
if (loaded != null) {
mButtonList.addAll(loaded);
}
mAdapter.notifyDataSetChanged();
}
private void showContextMenu(final int position) {
final TermuxButtonModel model = mButtonList.get(position);
final String[] items = new String[]{
getString(R.string.menu_execute),
getString(R.string.menu_edit),
getString(R.string.menu_delete),
getString(R.string.menu_create_shortcut),
getString(R.string.menu_cancel)
};
new AlertDialog.Builder(this)
.setTitle(model.getButtonName())
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getButtonName(), model.getExeCommand(),
model.getWorkDir(), true);
} else if (which == 1) {
showButtonDialog(position, model);
} else if (which == 2) {
showDeleteConfirmDialog(position);
} else if (which == 3) {
createDesktopShortcut(model);
}
}
})
.show();
}
private Bitmap loadButtonIcon(TermuxButtonModel model) {
String iconPath = model.getIconPath();
if (iconPath == null || iconPath.length() == 0) return null;
File iconFile = getIconFile(iconPath);
if (!iconFile.exists()) return null;
return BitmapFactory.decodeFile(iconFile.getAbsolutePath());
}
private void createDesktopShortcut(TermuxButtonModel model) {
Intent shortcutIntent = new Intent(this, MyTermuxActivity.class);
shortcutIntent.setAction(ACTION_EXECUTE_SHORTCUT);
shortcutIntent.putExtra(EXTRA_BUTTON_NAME, model.getButtonName());
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
ShortcutManager manager = getSystemService(ShortcutManager.class);
if (manager == null || !manager.isRequestPinShortcutSupported()) {
Toast.makeText(this, R.string.toast_shortcut_not_supported,
Toast.LENGTH_SHORT).show();
return;
}
Icon icon;
Bitmap bmp = loadButtonIcon(model);
if (bmp != null) {
icon = Icon.createWithBitmap(bmp);
} else {
icon = Icon.createWithResource(this,
android.R.drawable.ic_menu_manage);
}
String shortcutId = "termux_" + model.getButtonName();
android.content.pm.ShortcutInfo info =
new android.content.pm.ShortcutInfo.Builder(this, shortcutId)
.setShortLabel(model.getButtonName())
.setLongLabel(model.getButtonName())
.setIcon(icon)
.setIntent(shortcutIntent)
.build();
manager.requestPinShortcut(info, null);
} catch (Exception e) {
LogUtils.e(TAG, "createDesktopShortcut error: " + e.getMessage());
Toast.makeText(this, R.string.toast_shortcut_failed,
Toast.LENGTH_SHORT).show();
}
} else {
try {
Intent installIntent =
new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
model.getButtonName());
Bitmap bmp = loadButtonIcon(model);
if (bmp != null) {
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bmp);
} else {
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this,
android.R.drawable.ic_menu_manage));
}
installIntent.putExtra("duplicate", false);
sendBroadcast(installIntent);
} catch (Exception e) {
LogUtils.e(TAG, "createDesktopShortcut error: " + e.getMessage());
Toast.makeText(this, R.string.toast_shortcut_failed,
Toast.LENGTH_SHORT).show();
}
}
}
private void showDeleteConfirmDialog(final int position) {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.dialog_delete_title))
.setMessage(getString(R.string.dialog_delete_message) + mButtonList.get(position).getButtonName())
.setPositiveButton(getString(R.string.dialog_confirm), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxButtonManager.deleteButton(MyTermuxActivity.this, mButtonList, position);
refreshList();
Toast.makeText(MyTermuxActivity.this, R.string.toast_deleted, Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PICK_ICON && resultCode == RESULT_OK
&& data != null && data.getData() != null) {
Uri uri = data.getData();
String fileName = copyIconToAppStorage(uri);
if (fileName != null) {
mDialogIconPath = fileName;
File iconFile = getIconFile(fileName);
if (iconFile.exists() && mDialogIconView != null) {
Bitmap bitmap = BitmapFactory.decodeFile(
iconFile.getAbsolutePath());
if (bitmap != null) {
mDialogIconView.setImageBitmap(bitmap);
}
}
} else {
Toast.makeText(this, R.string.toast_icon_copy_failed,
Toast.LENGTH_SHORT).show();
}
}
}
private String copyIconToAppStorage(Uri uri) {
try {
File iconDir = new File(getFilesDir(), "termux_icons");
if (!iconDir.exists()) {
iconDir.mkdirs();
}
String fileName = System.currentTimeMillis() + ".png";
File destFile = new File(iconDir, fileName);
InputStream in = null;
FileOutputStream out = null;
try {
in = getContentResolver().openInputStream(uri);
out = new FileOutputStream(destFile);
byte[] buffer = new byte[8192];
int count;
while ((count = in.read(buffer)) > 0) {
out.write(buffer, 0, count);
}
} finally {
if (in != null) {
try { in.close(); } catch (Exception ignored) {}
}
if (out != null) {
try { out.close(); } catch (Exception ignored) {}
}
}
return fileName;
} catch (Exception e) {
LogUtils.e(TAG, "copyIconToAppStorage error: " + e.getMessage());
return null;
}
}
private File getIconFile(String iconPath) {
return new File(getFilesDir(), "termux_icons/" + iconPath);
}
private void showButtonDialog(final int index, final TermuxButtonModel model) {
final boolean isEdit = (model != null);
mDialogIconPath = (model != null && model.getIconPath() != null)
? model.getIconPath() : "";
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
final EditText etName = new EditText(this);
etName.setHint(R.string.hint_button_name);
if (model != null) {
etName.setText(model.getButtonName());
}
layout.addView(etName);
final EditText etCommand = new EditText(this);
etCommand.setHint(R.string.hint_exe_command);
if (model != null) {
etCommand.setText(model.getExeCommand());
}
layout.addView(etCommand);
final EditText etWorkDir = new EditText(this);
etWorkDir.setHint(R.string.hint_work_dir);
if (model != null) {
etWorkDir.setText(model.getWorkDir());
}
layout.addView(etWorkDir);
// Icon section
final TextView tvIconLabel = new TextView(this);
tvIconLabel.setText(R.string.label_icon);
tvIconLabel.setTextSize(14);
tvIconLabel.setTextColor(Color.GRAY);
layout.addView(tvIconLabel);
LinearLayout iconRow = new LinearLayout(this);
iconRow.setOrientation(LinearLayout.HORIZONTAL);
final ImageView ivIcon = new ImageView(this);
LinearLayout.LayoutParams iconLp = new LinearLayout.LayoutParams(100, 100);
iconLp.setMargins(0, 8, 16, 8);
ivIcon.setLayoutParams(iconLp);
ivIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
if (mDialogIconPath.length() > 0) {
File iconFile = getIconFile(mDialogIconPath);
if (iconFile.exists()) {
ivIcon.setImageBitmap(BitmapFactory.decodeFile(
iconFile.getAbsolutePath()));
}
}
iconRow.addView(ivIcon);
mDialogIconView = ivIcon;
LinearLayout iconBtnLayout = new LinearLayout(this);
iconBtnLayout.setOrientation(LinearLayout.VERTICAL);
Button btnSelectIcon = new Button(this);
btnSelectIcon.setText(R.string.btn_select_icon);
btnSelectIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_PICK_ICON);
}
});
iconBtnLayout.addView(btnSelectIcon);
Button btnClearIcon = new Button(this);
btnClearIcon.setText(R.string.btn_clear_icon);
btnClearIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDialogIconPath = "";
mDialogIconView.setImageDrawable(null);
}
});
iconBtnLayout.addView(btnClearIcon);
iconRow.addView(iconBtnLayout);
layout.addView(iconRow);
int titleResId = isEdit ? R.string.dialog_edit_title : R.string.dialog_add_title;
new AlertDialog.Builder(this)
.setTitle(titleResId)
.setView(layout)
.setPositiveButton(getString(R.string.dialog_save), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = etName.getText().toString().trim();
String command = etCommand.getText().toString().trim();
String workDir = etWorkDir.getText().toString().trim();
if (name.isEmpty() || command.isEmpty()) {
Toast.makeText(MyTermuxActivity.this, R.string.toast_fields_required, Toast.LENGTH_SHORT).show();
return;
}
TermuxButtonModel newModel = new TermuxButtonModel();
newModel.setButtonName(name);
newModel.setExeCommand(command);
newModel.setWorkDir(workDir);
newModel.setIconPath(mDialogIconPath);
if (isEdit) {
TermuxButtonManager.updateButton(MyTermuxActivity.this, mButtonList, index, newModel);
} else {
TermuxButtonManager.addButton(MyTermuxActivity.this, mButtonList, newModel);
}
refreshList();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
private class ButtonAdapter extends BaseAdapter {
@Override
public int getCount() {
return mButtonList.size();
}
@Override
public Object getItem(int position) {
return mButtonList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView ivIcon;
TextView tv;
if (convertView instanceof LinearLayout) {
LinearLayout ll = (LinearLayout) convertView;
ivIcon = (ImageView) ll.getChildAt(0);
tv = (TextView) ll.getChildAt(1);
} else {
LinearLayout ll = new LinearLayout(MyTermuxActivity.this);
ll.setOrientation(LinearLayout.HORIZONTAL);
ll.setGravity(Gravity.CENTER_VERTICAL);
ll.setPadding(30, 16, 30, 16);
ivIcon = new ImageView(MyTermuxActivity.this);
ivIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
ivIcon.setVisibility(View.GONE);
ll.addView(ivIcon);
tv = new TextView(MyTermuxActivity.this);
tv.setTextSize(16);
tv.setMinHeight(80);
tv.setGravity(Gravity.CENTER_VERTICAL);
tv.setPadding(16, 0, 0, 0);
ll.addView(tv);
convertView = ll;
}
TermuxButtonModel model = mButtonList.get(position);
String name = model.getButtonName();
String cmd = model.getExeCommand();
String fullText = name + "\n" + cmd;
SpannableString sp = new SpannableString(fullText);
sp.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sp.setSpan(new ForegroundColorSpan(Color.BLUE), 0, name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(sp);
String iconPath = model.getIconPath();
if (iconPath != null && iconPath.length() > 0) {
File iconFile = getIconFile(iconPath);
if (iconFile.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(
iconFile.getAbsolutePath());
if (bitmap != null) {
float density = getResources()
.getDisplayMetrics().density;
int marginPx = (int) (5 * density + 0.5f);
int estHeight = tv.getMinHeight();
int iconWidth = estHeight * bitmap.getWidth()
/ bitmap.getHeight();
iconWidth = Math.max(iconWidth, (int)(32*density));
iconWidth = Math.min(iconWidth, (int)(120*density));
ivIcon.setImageBitmap(bitmap);
ivIcon.setScaleType(
ImageView.ScaleType.FIT_CENTER);
LinearLayout.LayoutParams lp =
new LinearLayout.LayoutParams(iconWidth,
LinearLayout.LayoutParams.MATCH_PARENT);
lp.setMargins(marginPx, marginPx,
marginPx, marginPx);
ivIcon.setLayoutParams(lp);
ivIcon.setVisibility(View.VISIBLE);
} else {
ivIcon.setImageDrawable(null);
ivIcon.setVisibility(View.GONE);
}
} else {
ivIcon.setImageDrawable(null);
ivIcon.setVisibility(View.GONE);
}
} else {
ivIcon.setImageDrawable(null);
ivIcon.setVisibility(View.GONE);
}
return convertView;
}
}
}

View File

@@ -1,12 +1,27 @@
package cc.winboll.studio.winboll.termux;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import cc.winboll.studio.libappbase.LogUtils; // 替换 Log 为 LogUtils与 Activity 一致)
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.widget.TextView;
import android.widget.Toast;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.shell.command.ExecutionCommand.Runner;
import java.util.concurrent.Executor;
/**
* Termux 命令调用工具类(基于 RunCommandService 原型封装)
@@ -77,7 +92,12 @@ public class TermuxCommandExecutor {
LogUtils.d(TAG, "结果输出目录:" + resultDir);
}
// 7. 允许替换参数中的逗号替代字符
// 7. 强制创建新终端会话(而非复用已有会话),避免第二次点击直接弹出旧窗口
if (!isBackground) {
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", 0);
}
// 8. 允许替换参数中的逗号替代字符
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true);
// 8. 发送请求(区分 Android O 及以上的前台服务)
@@ -175,11 +195,23 @@ public class TermuxCommandExecutor {
return tip;
}
public static boolean openTermuxBash(Context context, String command) {
return openTermuxBash(context, command, "~");
}
private static String pendingTargetCmd;
private static String pendingDisplayCmd;
private static String pendingDisplayName;
public static boolean openTermuxBash(Context context, String command, String workDir) {
public static boolean openTermuxBash(Context context, String command) {
return openTermuxBash(context, null, command, "~", true);
}
public static boolean openTermuxBash(Context context, String command, String workDir) {
return openTermuxBash(context, null, command, workDir, true);
}
public static boolean openTermuxBash(Context context, String command, String workDir, boolean keepAlive) {
return openTermuxBash(context, null, command, workDir, keepAlive);
}
public static boolean openTermuxBash(Context context, String displayName, String command, String workDir, boolean keepAlive) {
LogUtils.d(TAG, "openTermuxBash() 按钮点击执行Gradle命令实时输出");
// 1. 校验Termux是否安装
@@ -189,10 +221,10 @@ public class TermuxCommandExecutor {
}
// 2. 定义核心路径确保路径与Termux中一致
String projectPath = TERMUX_HOME_PATH;
if (workDir.startsWith("~") || workDir.startsWith(".")) {
projectPath = TERMUX_HOME_PATH + "/" + workDir.substring(1);
}
String projectPath = TERMUX_HOME_PATH;
if (workDir.startsWith("~") || workDir.startsWith(".")) {
projectPath = TERMUX_HOME_PATH + "/" + workDir.substring(1);
}
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
@@ -202,21 +234,121 @@ public class TermuxCommandExecutor {
targetCmd += "source ~/.bashrc && ";
// 步骤3显式配置PATH
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:$PATH && ";
// 步骤4用stdbuf禁用stdout/stderr缓冲关键执行Gradle命令
// -o0stdout无缓冲-e0stderr无缓冲-i0stdin无缓冲
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
// 步骤5执行成功提示
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 当前目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
// 4. 执行命令终端会话模式唤起Termux窗口
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(context, targetCmd);
if (!cmdSuccess) {
return true;
// 步骤4将用户输入的字面\n转换为shell命令分隔符
// 确保"cd ~/Sources\npwd"这类输入能分段执行
String execCommand = command.replace("\\n", "; ");
// 步骤5执行设定的命令直接由外层bash解释避免stdbuf对shell内置命令无效
targetCmd += execCommand;
// 步骤6需要保持终端可见时追加交互式bash
if (keepAlive) {
targetCmd += "; stdbuf -o0 -e0 -i0 bash";
}
return false;
// 步骤7指纹验证后执行命令
if (context instanceof FragmentActivity) {
pendingTargetCmd = targetCmd;
pendingDisplayCmd = execCommand;
pendingDisplayName = displayName;
showFingerprintAndExecute((FragmentActivity) context);
return true;
} else {
return TermuxCommandExecutor.executeTerminalCommand(context, targetCmd);
}
}
private static void showFingerprintAndExecute(final FragmentActivity activity) {
String displayName = pendingDisplayName != null
? pendingDisplayName : "";
final StringBuilder sb = new StringBuilder();
if (pendingDisplayCmd != null) {
sb.append(pendingDisplayCmd);
}
final String cmdText = sb.toString();
SpannableString message = new SpannableString(
activity.getString(R.string.biometric_description,
displayName, cmdText));
int nameIdx = message.toString().indexOf(displayName);
if (nameIdx >= 0 && displayName.length() > 0) {
message.setSpan(new StyleSpan(Typeface.BOLD), nameIdx,
nameIdx + displayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
message.setSpan(new ForegroundColorSpan(Color.BLUE), nameIdx,
nameIdx + displayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
final AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle(R.string.biometric_title)
.setMessage(message)
.setPositiveButton(R.string.biometric_start,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startBiometricAuth(activity);
}
})
.setNegativeButton(R.string.dialog_cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
clearPending();
}
})
.setCancelable(false)
.show();
TextView tv = (TextView) dialog.findViewById(android.R.id.message);
if (tv != null) {
tv.setText(message);
}
}
private static void startBiometricAuth(final FragmentActivity activity) {
Executor executor = ContextCompat.getMainExecutor(activity);
final BiometricPrompt biometricPrompt = new BiometricPrompt(activity,
executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
executePendingCommand(activity);
}
@Override
public void onAuthenticationError(int errorCode,
CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
clearPending();
Toast.makeText(activity, R.string.toast_auth_failed,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
});
BiometricPrompt.PromptInfo promptInfo =
new BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(R.string.biometric_title))
.setNegativeButtonText(activity.getString(R.string.dialog_cancel))
.setConfirmationRequired(false)
.build();
biometricPrompt.authenticate(promptInfo);
}
private static void executePendingCommand(Context context) {
if (pendingTargetCmd != null) {
String cmd = pendingTargetCmd;
clearPending();
executeTerminalCommand(context, cmd);
}
}
private static void clearPending() {
pendingTargetCmd = null;
pendingDisplayCmd = null;
}
}

View File

@@ -11,10 +11,11 @@
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- <cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> -->
</LinearLayout>

View File

@@ -1,3 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_create_shortcut">创建桌面快捷方式</string>
<string name="toast_shortcut_not_supported">系统不支持创建桌面快捷方式</string>
<string name="toast_shortcut_failed">创建桌面快捷方式失败</string>
<string name="toast_shortcut_not_found">未找到对应的按钮请先打开MyTermuxActivity</string>
<string name="toast_auth_failed">指纹验证失败</string>
<string name="biometric_title">指纹验证</string>
<string name="biometric_description">名称:%1$s\n命令%2$s</string>
<string name="biometric_start">开始指纹验证</string>
<string name="label_icon">图标</string>
<string name="btn_select_icon">选择图标</string>
<string name="btn_clear_icon">清除图标</string>
<string name="toast_icon_copy_failed">图标导入失败</string>
</resources>

View File

@@ -17,6 +17,7 @@
<string name="menu_execute">执行</string>
<string name="menu_edit">编辑</string>
<string name="menu_delete">删除</string>
<string name="menu_create_shortcut">创建桌面快捷方式</string>
<string name="menu_cancel">取消</string>
<string name="dialog_delete_title">确认删除</string>
<string name="dialog_delete_message">确定要删除</string>
@@ -30,4 +31,15 @@
<string name="hint_work_dir">工作目录(默认 ~</string>
<string name="toast_deleted">已删除</string>
<string name="toast_fields_required">按钮名称和执行命令不能为空</string>
<string name="toast_shortcut_not_supported">系统不支持创建桌面快捷方式</string>
<string name="toast_shortcut_failed">创建桌面快捷方式失败</string>
<string name="toast_shortcut_not_found">未找到对应的按钮请先打开MyTermuxActivity</string>
<string name="toast_auth_failed">指纹验证失败</string>
<string name="biometric_title">指纹验证</string>
<string name="biometric_description">名称:%1$s\n命令%2$s</string>
<string name="biometric_start">开始指纹验证</string>
<string name="label_icon">Icon</string>
<string name="btn_select_icon">Select Icon</string>
<string name="btn_clear_icon">Clear Icon</string>
<string name="toast_icon_copy_failed">Failed to import icon</string>
</resources>