Compare commits
99 Commits
backup-bef
...
fd56e14fc8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd56e14fc8 | ||
|
|
557950f692 | ||
|
|
855352bfd9 | ||
|
|
454090248d | ||
|
|
25b9f5c52b | ||
|
|
67fad1d7f8 | ||
|
|
dcd3fcb9ee | ||
|
|
be2156a634 | ||
|
|
a5bd0a5b15 | ||
|
|
858b1c5f54 | ||
|
|
c52598bd44 | ||
|
|
195e61d810 | ||
|
|
544494df8c | ||
|
|
eaeeba2b0a | ||
|
|
3511b3f1ce | ||
|
|
32fe099fab | ||
|
|
78218ce1f5 | ||
|
|
854e80de80 | ||
|
|
2a97a7a790 | ||
|
|
cc23da6904 | ||
|
|
ad58737c67 | ||
|
|
9c3b628a83 | ||
|
|
9ed8b39d0b | ||
|
|
04818a909e | ||
|
|
2cd1fdd660 | ||
|
|
3069e4ec4d | ||
|
|
c15869e319 | ||
|
|
451cfb6502 | ||
|
|
7af30639d1 | ||
|
|
86dca84a27 | ||
|
|
66fa86b37d | ||
|
|
0605201b97 | ||
|
|
918c36ebc2 | ||
|
|
a6b0e1b41b | ||
|
|
b36af7f78a | ||
|
|
2643bf9cdf | ||
|
|
eeac0baa88 | ||
|
|
a6b5736f9a | ||
|
|
d557bdefa2 | ||
|
|
4ce1924299 | ||
|
|
63fdfb813e | ||
|
|
6d8f50267c | ||
|
|
eda34d24b1 | ||
|
|
3479cb25f2 | ||
|
|
1a7cefc630 | ||
|
|
4c054dd4a3 | ||
|
|
49ecd1e74e | ||
|
|
e60d4e1b42 | ||
|
|
ae8abc2908 | ||
|
|
df6f548c76 | ||
|
|
3492feb944 | ||
|
|
25ea8b7837 | ||
|
|
2e3ff489a0 | ||
|
|
c7705348b4 | ||
|
|
eff53e4156 | ||
|
|
342171e2ac | ||
|
|
61a6bf1e72 | ||
|
|
efdcd3f187 | ||
|
|
b79961b08e | ||
|
|
32a30babcc | ||
|
|
cb01591369 | ||
|
|
1c802f6948 | ||
|
|
49d73786bf | ||
|
|
d2abbcca9f | ||
|
|
d8a25aced9 | ||
|
|
bba3d800af | ||
|
|
7079b633bb | ||
|
|
8907a2d5d8 | ||
|
|
edb26dc593 | ||
|
|
b9409c9056 | ||
|
|
c92606246e | ||
|
|
47ca2f782f | ||
|
|
2b55e2bdd4 | ||
|
|
f1e2ab7c37 | ||
|
|
a8cd7c193d | ||
|
|
f8463490e1 | ||
|
|
65367f0d06 | ||
|
|
1d31638073 | ||
|
|
96bbb77127 | ||
|
|
e394a0f078 | ||
|
|
3d0e57e813 | ||
|
|
b9d3dc89da | ||
|
|
7fd1430207 | ||
|
|
a5ecbec23b | ||
|
|
e9b6052c6a | ||
|
|
648b45585a | ||
|
|
3b6830e0ec | ||
|
|
09f2aa4d07 | ||
|
|
080cb9abf7 | ||
|
|
2a53c872eb | ||
|
|
efb14bcde0 | ||
|
|
c4b886f83d | ||
|
|
53967f77d2 | ||
|
|
46393fe9ba | ||
|
|
5a4e3f6423 | ||
|
|
c1643924b7 | ||
|
|
c41e1479b9 | ||
|
|
ea72f6b2eb | ||
|
|
a0b6b92f57 |
69
README.md
69
README.md
@@ -9,6 +9,12 @@ ShortX ToolHub 是一个面向 **ShortX / Rhino ES5 JS** 的模块化悬浮工
|
||||
https://git.xin-blog.com/linshenjianlu/ShortX_ToolHub
|
||||
```
|
||||
|
||||
GitHub 镜像地址:
|
||||
|
||||
```text
|
||||
https://github.com/7015725/Toolhub-FloatBall
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心特性
|
||||
@@ -18,8 +24,12 @@ https://git.xin-blog.com/linshenjianlu/ShortX_ToolHub
|
||||
- **SHA256 文件校验**:每个子模块按清单中的 `sha256` 和 `size` 校验,通过后才覆盖本地文件。
|
||||
- **防回滚**:入口内置 `MIN_TRUSTED_MANIFEST_VERSION`,并记录本地已信任清单版本,拒绝旧版本清单。
|
||||
- **本地可信回退**:网络或远端清单异常时,不盲目覆盖;已验证过的本地模块可继续使用。
|
||||
- **更新源切换**:入口支持在 Gitea 主源与 GitHub 镜像之间切换,下载流程仍统一走签名清单与 SHA256 校验。
|
||||
- **App 化设置页**:设置主页、按钮管理、按钮编辑等页面使用统一 Shell 与页面栈,支持顶部返回/关闭。
|
||||
- **系统返回适配**:支持导航键返回;悬浮窗内置左右边缘交互式滑动返回,拖动时实时露出上级页面,松手后完成返回。
|
||||
- **ShortX 图标选择器**:支持图标点选、搜索、分页、自适应列数,不再依赖手填图标名。
|
||||
- **颜色选择器**:支持常用色、最近色、RGB、透明度和实时预览。
|
||||
- **颜色选择器**:使用折叠式完整调色板,支持常用色、最近色、RGB、透明度和实时预览;避免重复内联色板。
|
||||
- **自适应布局**:ToolApp 根据屏幕尺寸调整宽高,按钮管理页底部操作区保持可见;悬浮球在横竖屏切换后会重新计算屏幕尺寸并保持吸边位置。
|
||||
- **日志记录**:启动、更新、验签、加载异常写入 `ToolHub/logs/init.log`。
|
||||
|
||||
---
|
||||
@@ -151,6 +161,13 @@ ShortX_ToolHub/
|
||||
9. 校验通过才覆盖本地模块。
|
||||
10. 所有模块加载正常后,保存本地可信清单版本。
|
||||
|
||||
入口顶部可通过 `UPDATE_SOURCE` 选择更新源:
|
||||
|
||||
- `0`:Gitea 主源
|
||||
- `1`:GitHub 镜像
|
||||
|
||||
无论选择哪个源,`manifest.json` / `manifest.sig` 验签、防回滚和子模块 SHA256 校验流程都保持一致。
|
||||
|
||||
当前 keyId:
|
||||
|
||||
```text
|
||||
@@ -176,13 +193,23 @@ toolhub-targets-2026-rsa3072
|
||||
| `th_11_action.js` | 按钮动作分发与执行 |
|
||||
| `th_12_rebuild.js` | 悬浮球重建逻辑 |
|
||||
| `th_13_panel_ui.js` | 设置面板通用 UI 组件 |
|
||||
| `th_14_panels.js` | 设置面板、按钮编辑器、图标选择器、颜色选择器 |
|
||||
| `th_15_extra.js` | 主面板与附加展示层 |
|
||||
| `th_16_entry.js` | 生命周期、广播注册、启动与销毁 |
|
||||
| `th_14_panels.js` | 设置子页面、按钮编辑器、图标选择器、颜色选择器 |
|
||||
| `th_15_extra.js` | 主面板、ToolApp 页面栈、设置主页、按钮管理页 |
|
||||
| `th_16_entry.js` | 生命周期、广播注册、系统返回处理、启动与销毁 |
|
||||
|
||||
---
|
||||
|
||||
## 图标与颜色交互
|
||||
## 图标、颜色与设置交互
|
||||
|
||||
### ToolApp 设置页
|
||||
|
||||
- 设置、按钮管理、按钮编辑统一运行在 ToolApp Shell 内。
|
||||
- 顶部栏提供返回与关闭,子页面优先通过页面栈返回。
|
||||
- 系统返回键会优先回到上一页;没有上一页时关闭 ToolApp。
|
||||
- 悬浮窗无法稳定接入系统级预测性返回动画,因此 ToolHub 内置左右边缘交互式滑动返回作为替代。
|
||||
- 拖动过程中当前页面跟随手指横移,上级页面在底层实时露出;松手达到阈值后完成返回,未达到阈值则回弹。
|
||||
- 面板尺寸按屏幕自适应,减少小屏溢出与大屏空白。
|
||||
- 按钮管理页底部操作区保持可见,避免被列表内容挤出屏幕。
|
||||
|
||||
### ShortX 图标选择器
|
||||
|
||||
@@ -258,6 +285,38 @@ ToolHub.js.sha256
|
||||
|
||||
## 更新记录
|
||||
|
||||
### 2026-05-19
|
||||
|
||||
**功能改进**
|
||||
|
||||
- README 补充 GitHub 镜像地址与更新源切换说明。
|
||||
- ToolHub 入口支持通过 `UPDATE_SOURCE` 在 Gitea 主源与 GitHub 镜像之间切换。
|
||||
- 主面板动物岛主题继续优化为更轻量的融合色视觉,避免高频主面板出现过重图标气泡。
|
||||
|
||||
**稳定性修复**
|
||||
|
||||
- 改进悬浮球横竖屏切换后的屏幕尺寸刷新与吸边重排,减少横屏时位置跑到屏幕中间的问题。
|
||||
- 修复长按打开设置后手指轻微移动导致设置页被普通拖拽逻辑关闭的问题。
|
||||
- 修复设置 schema 持久化导致旧用户看不到最新滑块范围或文案的问题。
|
||||
- 优化 ToolApp 页面内返回边界宽度设置,变更后可即时作用于当前页面。
|
||||
|
||||
### 2026-05-13
|
||||
|
||||
**功能改进**
|
||||
|
||||
- 新增 ToolApp 式设置主页与页面栈,设置、按钮管理、按钮编辑改为更接近 App 的层级导航。
|
||||
- 支持系统导航返回;因 ToolHub 是悬浮窗 Overlay,系统级预测性返回动画不稳定,改为内置左右边缘交互式滑动返回。
|
||||
- 边缘返回拖动时会预渲染上级页面,当前页面随手指横移并露出上级页面,松手后完成返回或回弹。
|
||||
- ToolApp 尺寸按屏幕自适应,按钮管理页底部操作区保持可见。
|
||||
- 按钮管理与按钮编辑布局继续轻量化,减少说明文字和视觉负担。
|
||||
- ShortX 图标选择器风格与设置 UI 对齐。
|
||||
- 移除重复内联颜色调色板,统一使用折叠式颜色选择器。
|
||||
|
||||
**稳定性修复**
|
||||
|
||||
- 改进 ToolHub 启动与清理流程。
|
||||
- 修复设置页返回按钮状态、编辑器返回后页面栈状态、系统手势返回提示等细节问题。
|
||||
|
||||
### 2026-05-09
|
||||
|
||||
**文档更新**
|
||||
|
||||
11
ToolHub.js
11
ToolHub.js
@@ -2,7 +2,16 @@
|
||||
// 安全更新机制:入口内置 RSA 公钥,先验证 manifest.json/manifest.sig,再按 SHA256 下载子模块。
|
||||
// Gitea 只负责分发;未通过签名/哈希/防回滚校验时,不覆盖本地模块。
|
||||
|
||||
var GIT_ROOT = "https://git.xin-blog.com/linshenjianlu/ShortX_ToolHub/raw/branch/main/";
|
||||
var UPDATE_SOURCE = 0; // 0: Gitea, 1: GitHub
|
||||
|
||||
var UPDATE_ROOTS = [
|
||||
"https://git.xin-blog.com/linshenjianlu/ShortX_ToolHub/raw/branch/main/",
|
||||
"https://raw.githubusercontent.com/7015725/Toolhub-FloatBall/main/"
|
||||
];
|
||||
|
||||
if (UPDATE_SOURCE !== 1) UPDATE_SOURCE = 0;
|
||||
|
||||
var GIT_ROOT = UPDATE_ROOTS[UPDATE_SOURCE];
|
||||
var GIT_BASE = GIT_ROOT + "code/";
|
||||
var TRUSTED_PUBLIC_KEYS = {
|
||||
"toolhub-targets-2026-rsa3072": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEApiyhtMDJce7dVCxH1/oDu8kbiECYoT5XXmXvR/XNYuJ/5FuL83SbpCQ3QmUnqkbfNyOFqnxac/qlbXJtx6eeSotLP1HmrKI0LGymgxG6b1FfGHBfIKNZfBLIvzVDQob+HJfshlsS1JRlW5Jhm25TMh8dJCQQQZWW/ZItbtOvPYbLwG8cnqEdX8gqyB304+r2l35GPTfxZIGEK/9PcE3AMuqwTolMJsBHtG61hmMdz3dzTTEZQoOcciGWuwr2ZW8XkF6f5SgWkC29ZxZqAxceK4FJ8BsYirpFQxVKyZ6eiYlpNiYz+pHLP2U7JTO6ImmT1rlYSS6xw2tlWf0xq72nuOPC+VzEivuEhnC4y9WBSvauRa/ViIDgQ3yXl2MajuAvGSVWRfZ5Gz5Up8PQD7vxmHT2r0fA4xq4GIvUvGCqOG/d1FRrlVyEuNhCZ7KgpEKPno7fLnC6/ftnYcN5ZNOSWwjWH/e4fBxM5s6RRIYzIY2N0f/fqsRH42lWAhX5stujAgMBAAE="
|
||||
|
||||
@@ -1 +1 @@
|
||||
55dbcacaaa31b031e9a0fcef1253c2e0403fca423ad969e0e1387815e69de3e7 ToolHub.js
|
||||
458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js
|
||||
|
||||
@@ -83,6 +83,7 @@ var ConfigValidator = {
|
||||
PANEL_PADDING_DP: { type: "int", min: 8, max: 32, default: 12 },
|
||||
|
||||
// 主题配置
|
||||
SETTINGS_THEME: { type: "enum", values: ["animal", "monet"], default: "animal" },
|
||||
THEME_MODE: { type: "enum", values: [0, 1, 2], default: 1 },
|
||||
THEME_ACCENT_LIGHT: { type: "string", default: "#FF3A86FF" },
|
||||
THEME_ACCENT_DARK: { type: "string", default: "#FF90CAF9" },
|
||||
@@ -90,14 +91,20 @@ var ConfigValidator = {
|
||||
// 图标配置
|
||||
BALL_ICON_TYPE: { type: "enum", values: ["app", "file", "android", "shortx"], default: "app" },
|
||||
BALL_ICON_RES_ID: { type: "int", min: 0, max: 999999, default: 0 },
|
||||
BALL_ICON_SIZE_DP: { type: "int", min: 16, max: 64, default: 22 },
|
||||
BALL_ICON_SIZE_DP: { type: "int", min: 12, max: 80, default: 22 },
|
||||
BALL_PNG_MODE: { type: "int", min: 0, max: 2, default: 1 },
|
||||
BALL_IDLE_ALPHA: { type: "float", min: 0.1, max: 1.0, default: 0.6 },
|
||||
|
||||
// 交互配置
|
||||
LONG_PRESS_MS: { type: "int", min: 200, max: 2000, default: 600 },
|
||||
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: { type: "int", min: 8, max: 80, default: 28 },
|
||||
LONG_PRESS_VIBRATE_MS: { type: "int", min: 10, max: 100, default: 40 },
|
||||
CLICK_SLOP_DP: { type: "int", min: 2, max: 20, default: 6 },
|
||||
TOOLAPP_BACK_EDGE_WIDTH_DP: { type: "int", min: 1, max: 120, default: 24 },
|
||||
ENABLE_TOOLAPP_INNER_BACK_STRIPS: { type: "bool", default: false },
|
||||
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: { type: "bool", default: false },
|
||||
TOOLAPP_BACK_COMMIT_DISTANCE_DP: { type: "int", min: 1, max: 480, default: 72 },
|
||||
TOOLAPP_BACK_PROGRESS_DISTANCE_DP: { type: "int", min: 1, max: 720, default: 180 },
|
||||
|
||||
// 功能开关
|
||||
ENABLE_SNAP_TO_EDGE: { type: "bool", default: true },
|
||||
@@ -129,6 +136,7 @@ var ConfigValidator = {
|
||||
BALL_ICON_PKG: { type: "string", default: "" },
|
||||
BALL_ICON_RES_NAME: { type: "string", default: "" },
|
||||
BALL_ICON_TINT_HEX: { type: "string", default: "" },
|
||||
BALL_BG_COLOR_HEX: { type: "string", default: "" },
|
||||
|
||||
// 回弹动画配置
|
||||
BOUNCE_DECAY: { type: "float", min: 0.3, max: 0.95, default: 0.72 },
|
||||
@@ -726,7 +734,13 @@ var ConfigManager = {
|
||||
LONG_PRESS_VIBRATE_MS: 18,
|
||||
ENABLE_LONG_PRESS: true,
|
||||
LONG_PRESS_MS: 520,
|
||||
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: 28,
|
||||
CLICK_SLOP_DP: 6,
|
||||
TOOLAPP_BACK_EDGE_WIDTH_DP: 24,
|
||||
ENABLE_TOOLAPP_INNER_BACK_STRIPS: false,
|
||||
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: false,
|
||||
TOOLAPP_BACK_COMMIT_DISTANCE_DP: 72,
|
||||
TOOLAPP_BACK_PROGRESS_DISTANCE_DP: 180,
|
||||
ENABLE_BOUNCE: true,
|
||||
BOUNCE_TIMES: 2,
|
||||
BOUNCE_MAX_SCALE: 0.88,
|
||||
@@ -740,6 +754,7 @@ var ConfigManager = {
|
||||
BALL_ICON_RES_NAME: "",
|
||||
BALL_ICON_SIZE_DP: 22,
|
||||
BALL_ICON_TINT_HEX: "",
|
||||
BALL_BG_COLOR_HEX: "",
|
||||
BALL_IDLE_ALPHA: 0.6,
|
||||
PANEL_POS_GRAVITY: "bottom",
|
||||
PANEL_CUSTOM_OFFSET_Y: 0,
|
||||
@@ -754,6 +769,7 @@ var ConfigManager = {
|
||||
PANEL_LABEL_TOP_MARGIN_DP: 4,
|
||||
PANEL_BG_FALLBACK_HEX: "#EE1E1E1E",
|
||||
PANEL_BG_ALPHA: 0.85,
|
||||
SETTINGS_THEME: "animal",
|
||||
THEME_MODE: 1,
|
||||
THEME_DAY_BG_HEX: null,
|
||||
THEME_DAY_TEXT_HEX: null,
|
||||
@@ -770,6 +786,10 @@ var ConfigManager = {
|
||||
],
|
||||
defaultSchema: [
|
||||
{ type: "section", name: "外观" },
|
||||
{ key: "SETTINGS_THEME", name: "设置页主题", type: "single_choice", options: [
|
||||
{ label: "动物岛风", value: "animal" },
|
||||
{ label: "系统莫奈色", value: "monet" }
|
||||
]},
|
||||
{ key: "THEME_MODE", name: "主题(0跟随/1白/2黑)", type: "int", min: 0, max: 2, step: 1 },
|
||||
{ key: "THEME_DAY_BG_HEX", name: "日间背景色(#RRGGBB)", type: "text" },
|
||||
{ key: "THEME_DAY_TEXT_HEX", name: "日间文字色(#RRGGBB)", type: "text" },
|
||||
@@ -778,17 +798,19 @@ var ConfigManager = {
|
||||
{ key: "PANEL_BG_ALPHA", name: "面板背景透明度(0.1~1.0)", type: "float", min: 0.1, max: 1.0, step: 0.05 },
|
||||
|
||||
{ type: "section", name: "悬浮球" },
|
||||
{ key: "BALL_SIZE_DP", name: "悬浮球大小(dp)", type: "int", min: 28, max: 120, step: 1 },
|
||||
{ key: "BALL_PANEL_GAP_DP", name: "球与面板间距(dp)", type: "int", min: 0, max: 60, step: 1 },
|
||||
{ key: "BALL_ICON_TYPE", name: "图标类型", type: "single_choice", options: [
|
||||
{ label: "应用图标 (app)", value: "app" },
|
||||
{ label: "文件图标 (file)", value: "file" },
|
||||
{ label: "ShortX内置 (shortx)", value: "shortx" }
|
||||
{ key: "BALL_SIZE_DP", name: "气球大小", type: "int", min: 28, max: 120, step: 1 },
|
||||
{ key: "BALL_PANEL_GAP_DP", name: "离小屋的距离", type: "int", min: 0, max: 60, step: 1 },
|
||||
{ key: "BALL_ICON_TYPE", name: "气球徽章", type: "single_choice", options: [
|
||||
{ label: "应用头像", value: "app" },
|
||||
{ label: "从文件选择", value: "file" },
|
||||
{ label: "岛上图标库", value: "shortx" }
|
||||
]},
|
||||
{ key: "BALL_ICON_FILE_PATH", name: "图标路径(file模式)", type: "text" },
|
||||
{ key: "BALL_ICON_RES_NAME", name: "ShortX图标", type: "ball_shortx_icon" },
|
||||
{ key: "BALL_ICON_TINT_HEX", name: "图标颜色", type: "ball_color" },
|
||||
{ key: "BALL_IDLE_ALPHA", name: "闲置不透明度(0.1~1.0)", type: "float", min: 0.1, max: 1.0, step: 0.05 },
|
||||
{ key: "BALL_ICON_FILE_PATH", name: "已选择的图标文件", type: "text" },
|
||||
{ key: "BALL_ICON_RES_NAME", name: "岛上图标", type: "ball_shortx_icon" },
|
||||
{ key: "BALL_ICON_TINT_HEX", name: "徽章颜色", type: "ball_color" },
|
||||
{ key: "BALL_ICON_SIZE_DP", name: "徽章大小", type: "int", min: 12, max: 80, step: 1 },
|
||||
{ key: "BALL_BG_COLOR_HEX", name: "球体背景", type: "ball_color" },
|
||||
{ key: "BALL_IDLE_ALPHA", name: "安静时透明度", type: "float", min: 0.1, max: 1.0, step: 0.05 },
|
||||
|
||||
{ type: "section", name: "面板布局" },
|
||||
{ key: "PANEL_ROWS", name: "面板可视行数(超出滚动)", type: "int", min: 1, max: 12, step: 1 },
|
||||
@@ -828,8 +850,14 @@ var ConfigManager = {
|
||||
|
||||
{ type: "section", name: "触摸与手势" },
|
||||
{ key: "CLICK_SLOP_DP", name: "点击位移阈值(dp)", type: "int", min: 1, max: 40, step: 1 },
|
||||
{ key: "TOOLAPP_BACK_EDGE_WIDTH_DP", name: "返回起手边缘宽度", type: "int", min: 1, max: 120, step: 1 },
|
||||
{ key: "ENABLE_TOOLAPP_INNER_BACK_STRIPS", name: "启用旧版页面覆盖热区(不推荐)", type: "bool" },
|
||||
{ key: "ENABLE_TOOLAPP_SCREEN_BACK_STRIPS", name: "启用旧版屏幕覆盖热区(已禁用)", type: "bool" },
|
||||
{ key: "TOOLAPP_BACK_COMMIT_DISTANCE_DP", name: "设置页返回触发距离", type: "int", min: 1, max: 480, step: 1 },
|
||||
{ key: "TOOLAPP_BACK_PROGRESS_DISTANCE_DP", name: "设置页返回动画距离", type: "int", min: 1, max: 720, step: 1 },
|
||||
{ key: "ENABLE_LONG_PRESS", name: "启用长按", type: "bool" },
|
||||
{ key: "LONG_PRESS_MS", name: "长按判定(ms)", type: "int", min: 200, max: 2000, step: 10 },
|
||||
{ key: "LONG_PRESS_TRIGGERED_MOVE_SLOP_DP", name: "长按后抖动容忍距离", type: "int", min: 8, max: 80, step: 1 },
|
||||
{ key: "LONG_PRESS_HAPTIC_ENABLE", name: "长按震动反馈", type: "bool" },
|
||||
{ key: "LONG_PRESS_VIBRATE_MS", name: "震动时长(ms)", type: "int", min: 1, max: 120, step: 1 },
|
||||
|
||||
@@ -864,9 +892,46 @@ var ConfigManager = {
|
||||
var needReset = false;
|
||||
if (s) {
|
||||
var sStr = JSON.stringify(s);
|
||||
if (sStr.indexOf("ENABLE_SNAP_TO_EDGE") < 0 || sStr.indexOf("ENABLE_ANIMATIONS") < 0 || sStr.indexOf("BALL_IDLE_ALPHA") < 0 || sStr.indexOf("PANEL_POS_GRAVITY") < 0 || sStr.indexOf("single_choice") < 0 || sStr.indexOf("ball_shortx_icon") < 0 || sStr.indexOf("ball_color") < 0) {
|
||||
if (sStr.indexOf("ENABLE_SNAP_TO_EDGE") < 0 || sStr.indexOf("ENABLE_ANIMATIONS") < 0 || sStr.indexOf("BALL_IDLE_ALPHA") < 0 || sStr.indexOf("PANEL_POS_GRAVITY") < 0 || sStr.indexOf("single_choice") < 0 || sStr.indexOf("ball_shortx_icon") < 0 || sStr.indexOf("ball_color") < 0 || sStr.indexOf("SETTINGS_THEME") < 0 || sStr.indexOf("BALL_BG_COLOR_HEX") < 0 || sStr.indexOf("BALL_ICON_SIZE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_EDGE_WIDTH_DP") < 0 || sStr.indexOf("ENABLE_TOOLAPP_INNER_BACK_STRIPS") < 0 || sStr.indexOf("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS") < 0 || sStr.indexOf("TOOLAPP_BACK_COMMIT_DISTANCE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_PROGRESS_DISTANCE_DP") < 0 || sStr.indexOf("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP") < 0) {
|
||||
needReset = true;
|
||||
}
|
||||
|
||||
// 旧 schema.json 可能已经含有 key,但 UI 文案/范围仍是旧值;关键字段不一致时也强制刷新
|
||||
var findSchemaItemByKey = function(arr, key) {
|
||||
if (!arr) return null;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var it = arr[i];
|
||||
if (it && it.key === key) return it;
|
||||
var child = null;
|
||||
if (it && it.children) child = findSchemaItemByKey(it.children, key);
|
||||
if (!child && it && it.items) child = findSchemaItemByKey(it.items, key);
|
||||
if (child) return child;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
var schemaItemDiffers = function(key, fields) {
|
||||
var cur = findSchemaItemByKey(s, key);
|
||||
var def = findSchemaItemByKey(ConfigManager.defaultSchema, key);
|
||||
if (!cur || !def) return true;
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
var f = fields[i];
|
||||
if (typeof def[f] !== "undefined" && String(cur[f]) !== String(def[f])) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!needReset) {
|
||||
if (schemaItemDiffers("BALL_ICON_TINT_HEX", ["name", "type"]) ||
|
||||
schemaItemDiffers("BALL_ICON_SIZE_DP", ["name", "type", "min", "max", "step"]) ||
|
||||
schemaItemDiffers("BALL_BG_COLOR_HEX", ["name", "type"]) ||
|
||||
schemaItemDiffers("TOOLAPP_BACK_EDGE_WIDTH_DP", ["name", "type", "min", "max", "step"]) ||
|
||||
schemaItemDiffers("ENABLE_TOOLAPP_INNER_BACK_STRIPS", ["name", "type"]) ||
|
||||
schemaItemDiffers("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS", ["name", "type"]) ||
|
||||
schemaItemDiffers("TOOLAPP_BACK_COMMIT_DISTANCE_DP", ["name", "type", "min", "max", "step"]) ||
|
||||
schemaItemDiffers("TOOLAPP_BACK_PROGRESS_DISTANCE_DP", ["name", "type", "min", "max", "step"]) ||
|
||||
schemaItemDiffers("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP", ["name", "type", "min", "max", "step"])) {
|
||||
needReset = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// # 仅当文件不存在时才标记为需要重置(新建),避免因读取失败导致覆盖
|
||||
try {
|
||||
|
||||
@@ -42,6 +42,26 @@ function FloatBallAppWM(logger) {
|
||||
viewerPanel: null,
|
||||
viewerPanelLp: null,
|
||||
viewerPanelType: null,
|
||||
panelBackCallbackEntries: [],
|
||||
predictiveBackIndicatorView: null,
|
||||
predictiveBackIndicatorLp: null,
|
||||
|
||||
// 设置类 UI App 化:单窗口页面栈(settings -> 子页面 -> 编辑页)
|
||||
toolAppActive: false,
|
||||
toolAppNavStack: [],
|
||||
toolAppRoute: null,
|
||||
toolAppRoot: null,
|
||||
toolAppBody: null,
|
||||
toolAppContentHost: null,
|
||||
toolAppBackPreviewView: null,
|
||||
toolAppBackPreviewRoute: null,
|
||||
toolAppBackPreviewReady: false,
|
||||
toolAppScreenBackStrips: [],
|
||||
toolAppTitleView: null,
|
||||
toolAppBackButton: null,
|
||||
settingsGroupKey: null,
|
||||
settingsHomeSelectedCategoryId: null,
|
||||
settingsHomeSelectedItemId: null,
|
||||
|
||||
mask: null,
|
||||
maskLp: null,
|
||||
@@ -71,6 +91,9 @@ function FloatBallAppWM(logger) {
|
||||
pendingUserCfg: null,
|
||||
pendingDirty: false,
|
||||
|
||||
// 按钮管理首页:搜索过滤状态
|
||||
buttonManagerQuery: "",
|
||||
|
||||
closing: false
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,101 @@
|
||||
// =======================【工具:屏幕/旋转】======================
|
||||
FloatBallAppWM.prototype.getScreenSizePx = function() {
|
||||
var m = new android.util.DisplayMetrics();
|
||||
try { this.state.wm.getDefaultDisplay().getRealMetrics(m); } catch (e) { this.state.wm.getDefaultDisplay().getMetrics(m); }
|
||||
return { w: m.widthPixels, h: m.heightPixels };
|
||||
var w = 0;
|
||||
var h = 0;
|
||||
|
||||
try {
|
||||
this.state.wm.getDefaultDisplay().getRealMetrics(m);
|
||||
w = Number(m.widthPixels || 0);
|
||||
h = Number(m.heightPixels || 0);
|
||||
} catch (e0) {
|
||||
try {
|
||||
this.state.wm.getDefaultDisplay().getMetrics(m);
|
||||
w = Number(m.widthPixels || 0);
|
||||
h = Number(m.heightPixels || 0);
|
||||
} catch (e1) {}
|
||||
}
|
||||
|
||||
try {
|
||||
var rot = this.getRotation ? this.getRotation() : -1;
|
||||
var isLandscape = (rot === android.view.Surface.ROTATION_90 || rot === android.view.Surface.ROTATION_270);
|
||||
var isPortrait = (rot === android.view.Surface.ROTATION_0 || rot === android.view.Surface.ROTATION_180);
|
||||
if (isLandscape && w > 0 && h > 0 && w < h) {
|
||||
var t = w; w = h; h = t;
|
||||
} else if (isPortrait && w > 0 && h > 0 && w > h) {
|
||||
var t2 = w; w = h; h = t2;
|
||||
}
|
||||
} catch (eRot) {}
|
||||
|
||||
if (w <= 0 || h <= 0) {
|
||||
try {
|
||||
var dm = context.getResources().getDisplayMetrics();
|
||||
if (w <= 0) w = Number(dm.widthPixels || 0);
|
||||
if (h <= 0) h = Number(dm.heightPixels || 0);
|
||||
} catch (eRes) {}
|
||||
}
|
||||
|
||||
return { w: Math.max(1, Math.floor(w)), h: Math.max(1, Math.floor(h)) };
|
||||
};
|
||||
FloatBallAppWM.prototype.getRotation = function() { try { return this.state.wm.getDefaultDisplay().getRotation(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } return -1; };
|
||||
|
||||
// =======================【工具:alpha/toast/vibrate】======================
|
||||
FloatBallAppWM.prototype.withAlpha = function(colorInt, alpha01) { var a = Math.floor(Number(alpha01) * 255); return (colorInt & 0x00FFFFFF) | (a << 24); };
|
||||
|
||||
// Animal Island Lite:ToolApp 专用视觉主题。只供设置/按钮管理/编辑页调用,避免覆盖悬浮球 Monet 取色。
|
||||
FloatBallAppWM.prototype.getAnimalIslandTheme = function() {
|
||||
var isDark = false;
|
||||
try { isDark = this.isDarkTheme(); } catch(eDark) { isDark = false; }
|
||||
var Color = android.graphics.Color;
|
||||
if (isDark) {
|
||||
return {
|
||||
bg: Color.parseColor("#2F4034"),
|
||||
bg2: Color.parseColor("#3C5142"),
|
||||
leaf: Color.parseColor("#4E6F5B"),
|
||||
card: Color.parseColor("#405243"),
|
||||
card2: Color.parseColor("#4A5D4E"),
|
||||
cream: Color.parseColor("#FFF1D2"),
|
||||
text: Color.parseColor("#FFF1D2"),
|
||||
sub: Color.parseColor("#E3CFA8"),
|
||||
brown: Color.parseColor("#D0AE7A"),
|
||||
primary: Color.parseColor("#8BD7A8"),
|
||||
primaryDeep: Color.parseColor("#5FB980"),
|
||||
primarySoft: Color.parseColor("#3F684F"),
|
||||
danger: Color.parseColor("#F0A08F"),
|
||||
dangerSoft: Color.parseColor("#68453C"),
|
||||
stroke: Color.parseColor("#8B754E"),
|
||||
onPrimary: Color.parseColor("#173524")
|
||||
};
|
||||
}
|
||||
return {
|
||||
bg: Color.parseColor("#A8DDB4"),
|
||||
bg2: Color.parseColor("#DDF3D8"),
|
||||
leaf: Color.parseColor("#7DC395"),
|
||||
card: Color.parseColor("#FFF9E6"),
|
||||
card2: Color.parseColor("#FFFFFF"),
|
||||
cream: Color.parseColor("#FFF9E6"),
|
||||
text: Color.parseColor("#5E472D"),
|
||||
sub: Color.parseColor("#7C5734"),
|
||||
brown: Color.parseColor("#8B643D"),
|
||||
primary: Color.parseColor("#19C8B9"),
|
||||
primaryDeep: Color.parseColor("#0E9E91"),
|
||||
primarySoft: Color.parseColor("#DDF7F2"),
|
||||
danger: Color.parseColor("#D86962"),
|
||||
dangerSoft: Color.parseColor("#FFE7E2"),
|
||||
stroke: Color.parseColor("#E0C79E"),
|
||||
onPrimary: Color.parseColor("#FFFFFF")
|
||||
};
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.createAnimalCardDrawable = function(fillColor, radiusDp) {
|
||||
var T = this.getAnimalIslandTheme();
|
||||
return this.ui.createStrokeDrawable(fillColor || T.card, this.withAlpha(T.stroke, this.isDarkTheme() ? 0.36 : 0.55), this.dp(1), this.dp(radiusDp || 18));
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.createAnimalButtonDrawable = function(normalColor, pressedColor, radiusDp) {
|
||||
return this.ui.createRippleDrawable(normalColor, pressedColor, this.dp(radiusDp || 18));
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.toast = function(msg) { try { android.widget.Toast.makeText(context, String(msg), 0).show(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } };
|
||||
FloatBallAppWM.prototype.vibrateOnce = function(ms) {
|
||||
if (!this.config.LONG_PRESS_HAPTIC_ENABLE) return;
|
||||
@@ -58,7 +146,12 @@ FloatBallAppWM.prototype.ui = {
|
||||
// Monet 扩展字段(供面板直接使用)
|
||||
_monetSurface: android.graphics.Color.parseColor("#F8F9FA"),
|
||||
_monetOnSurface: android.graphics.Color.parseColor("#1F1F1F"),
|
||||
_monetSurfaceVariant: android.graphics.Color.parseColor("#E1E3E1"),
|
||||
_monetSurfaceContainerLow: android.graphics.Color.parseColor("#F1F3F4"),
|
||||
_monetSurfaceContainer: android.graphics.Color.parseColor("#ECEEEF"),
|
||||
_monetSurfaceContainerHigh: android.graphics.Color.parseColor("#E6E8EA"),
|
||||
_monetOutline: android.graphics.Color.parseColor("#747775"),
|
||||
_monetOutlineVariant: android.graphics.Color.parseColor("#C4C7C5"),
|
||||
_monetOnPrimary: android.graphics.Color.parseColor("#FFFFFF"),
|
||||
_monetPrimaryContainer: android.graphics.Color.parseColor("#D3E3FD"),
|
||||
_monetOnPrimaryContainer: android.graphics.Color.parseColor("#041E49"),
|
||||
@@ -117,7 +210,12 @@ FloatBallAppWM.prototype.ui = {
|
||||
var rippleColor = app.withAlpha ? app.withAlpha(txtColor, 0.1) : 0x22888888;
|
||||
btn.setBackground(this.createTransparentRippleDrawable(rippleColor, app.dp(8)));
|
||||
btn.setOnClickListener(new android.view.View.OnClickListener({
|
||||
onClick: function(v) { app.touchActivity(); app.guardClick("ui_btn", INTERACTION_CONSTANTS.CLICK_COOLDOWN_MS, function(){ if(onClick) onClick(v); }); }
|
||||
onClick: function(v) {
|
||||
app.touchActivity();
|
||||
var guardKey = "ui_btn";
|
||||
try { guardKey = "ui_btn_" + String(java.lang.System.identityHashCode(v || btn)); } catch(eKey) {}
|
||||
app.guardClick(guardKey, INTERACTION_CONSTANTS.CLICK_COOLDOWN_MS, function(){ if(onClick) onClick(v); });
|
||||
}
|
||||
}));
|
||||
return btn;
|
||||
},
|
||||
@@ -135,7 +233,12 @@ FloatBallAppWM.prototype.ui = {
|
||||
btn.setBackground(this.createRippleDrawable(bgColor, pressedColor, app.dp(24)));
|
||||
try { btn.setElevation(app.dp(2)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
|
||||
btn.setOnClickListener(new android.view.View.OnClickListener({
|
||||
onClick: function(v) { app.touchActivity(); app.guardClick("ui_btn", INTERACTION_CONSTANTS.CLICK_COOLDOWN_MS, function(){ if(onClick) onClick(v); }); }
|
||||
onClick: function(v) {
|
||||
app.touchActivity();
|
||||
var guardKey = "ui_btn";
|
||||
try { guardKey = "ui_btn_" + String(java.lang.System.identityHashCode(v || btn)); } catch(eKey) {}
|
||||
app.guardClick(guardKey, INTERACTION_CONSTANTS.CLICK_COOLDOWN_MS, function(){ if(onClick) onClick(v); });
|
||||
}
|
||||
}));
|
||||
return btn;
|
||||
},
|
||||
@@ -188,21 +291,30 @@ FloatBallAppWM.prototype.ui = {
|
||||
var pasteBtn = this.createFlatButton(app, "粘贴", this.colors.accent, function() {
|
||||
try {
|
||||
var cb = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE);
|
||||
if (cb.hasPrimaryClip()) {
|
||||
var item = cb.getPrimaryClip().getItemAt(0);
|
||||
if (item) {
|
||||
if (!cb || !cb.hasPrimaryClip()) {
|
||||
app.toast("剪贴板为空");
|
||||
return;
|
||||
}
|
||||
var clip = cb.getPrimaryClip();
|
||||
if (!clip || clip.getItemCount() <= 0) {
|
||||
app.toast("剪贴板为空");
|
||||
return;
|
||||
}
|
||||
var item = clip.getItemAt(0);
|
||||
if (!item) {
|
||||
app.toast("剪贴板为空");
|
||||
return;
|
||||
}
|
||||
var txt = item.getText();
|
||||
if (txt) {
|
||||
if (txt === null || txt === undefined || String(txt).length === 0) {
|
||||
app.toast("剪贴板不是文本内容");
|
||||
return;
|
||||
}
|
||||
var st = String(txt);
|
||||
var old = String(et.getText());
|
||||
if (old.length > 0) et.setText(old + st);
|
||||
else et.setText(st);
|
||||
et.setSelection(et.getText().length());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.toast("剪贴板为空");
|
||||
}
|
||||
} catch (eP) {
|
||||
app.toast("粘贴失败: " + eP);
|
||||
}
|
||||
@@ -345,6 +457,9 @@ var MonetColorProvider = {
|
||||
surface: android.graphics.Color.parseColor("#F8F9FA"),
|
||||
onSurface: android.graphics.Color.parseColor("#1F1F1F"),
|
||||
surfaceVariant: android.graphics.Color.parseColor("#E1E3E1"),
|
||||
surfaceContainerLow: android.graphics.Color.parseColor("#F1F3F4"),
|
||||
surfaceContainer: android.graphics.Color.parseColor("#ECEEEF"),
|
||||
surfaceContainerHigh: android.graphics.Color.parseColor("#E6E8EA"),
|
||||
onSurfaceVariant: android.graphics.Color.parseColor("#5F6368"),
|
||||
outline: android.graphics.Color.parseColor("#747775"),
|
||||
outlineVariant: android.graphics.Color.parseColor("#C4C7C5"),
|
||||
@@ -366,6 +481,9 @@ var MonetColorProvider = {
|
||||
surface: android.graphics.Color.parseColor("#131314"),
|
||||
onSurface: android.graphics.Color.parseColor("#E3E3E3"),
|
||||
surfaceVariant: android.graphics.Color.parseColor("#49454F"),
|
||||
surfaceContainerLow: android.graphics.Color.parseColor("#1B1B1F"),
|
||||
surfaceContainer: android.graphics.Color.parseColor("#202124"),
|
||||
surfaceContainerHigh: android.graphics.Color.parseColor("#2B2C30"),
|
||||
onSurfaceVariant: android.graphics.Color.parseColor("#C4C7C5"),
|
||||
outline: android.graphics.Color.parseColor("#8E918F"),
|
||||
outlineVariant: android.graphics.Color.parseColor("#49454F"),
|
||||
@@ -390,6 +508,9 @@ var MonetColorProvider = {
|
||||
surface: "system_neutral1_900",
|
||||
onSurface: "system_neutral1_100",
|
||||
surfaceVariant: "system_neutral2_700",
|
||||
surfaceContainerLow: "system_neutral1_800",
|
||||
surfaceContainer: "system_neutral1_800",
|
||||
surfaceContainerHigh: "system_neutral1_700",
|
||||
onSurfaceVariant: "system_neutral2_200",
|
||||
outline: "system_neutral2_400",
|
||||
outlineVariant: "system_neutral2_700",
|
||||
@@ -407,6 +528,9 @@ var MonetColorProvider = {
|
||||
surface: "system_neutral1_10",
|
||||
onSurface: "system_neutral1_900",
|
||||
surfaceVariant: "system_neutral2_100",
|
||||
surfaceContainerLow: "system_neutral1_50",
|
||||
surfaceContainer: "system_neutral1_100",
|
||||
surfaceContainerHigh: "system_neutral1_200",
|
||||
onSurfaceVariant: "system_neutral2_700",
|
||||
outline: "system_neutral2_500",
|
||||
outlineVariant: "system_neutral2_200",
|
||||
@@ -558,19 +682,19 @@ FloatBallAppWM.prototype.refreshMonetColors = function(forceDark) {
|
||||
|
||||
// 浅色配色
|
||||
c.bgLight = ml.surface;
|
||||
c.cardLight = ml.surfaceVariant;
|
||||
c.cardLight = ml.surfaceContainerLow || ml.surfaceVariant;
|
||||
c.textPriLight = ml.onSurface;
|
||||
c.textSecLight = ml.onSurfaceVariant;
|
||||
c.dividerLight = ml.outline;
|
||||
c.inputBgLight = ml.surface;
|
||||
c.dividerLight = ml.outlineVariant || ml.outline;
|
||||
c.inputBgLight = ml.surfaceContainerHigh || ml.surface;
|
||||
|
||||
// 深色配色
|
||||
c.bgDark = md.surface;
|
||||
c.cardDark = md.surfaceVariant;
|
||||
c.cardDark = md.surfaceContainerLow || md.surfaceVariant;
|
||||
c.textPriDark = md.onSurface;
|
||||
c.textSecDark = md.onSurfaceVariant;
|
||||
c.dividerDark = md.outline;
|
||||
c.inputBgDark = md.surface;
|
||||
c.dividerDark = md.outlineVariant || md.outline;
|
||||
c.inputBgDark = md.surfaceContainerHigh || md.surface;
|
||||
|
||||
// 当前主题配色(随主题切换)
|
||||
c.primary = m.primary;
|
||||
@@ -585,7 +709,12 @@ FloatBallAppWM.prototype.refreshMonetColors = function(forceDark) {
|
||||
// 扩展:完整 Monet 语义字段(供面板方法直接使用)
|
||||
c._monetSurface = m.surface;
|
||||
c._monetOnSurface = m.onSurface;
|
||||
c._monetSurfaceVariant = m.surfaceVariant;
|
||||
c._monetSurfaceContainerLow = m.surfaceContainerLow || m.surfaceVariant;
|
||||
c._monetSurfaceContainer = m.surfaceContainer || m.surfaceVariant;
|
||||
c._monetSurfaceContainerHigh = m.surfaceContainerHigh || m.surfaceVariant;
|
||||
c._monetOutline = m.outline;
|
||||
c._monetOutlineVariant = m.outlineVariant || m.outline;
|
||||
c._monetOnPrimary = m.onPrimary;
|
||||
c._monetPrimaryContainer = m.primaryContainer;
|
||||
c._monetOnPrimaryContainer = m.onPrimaryContainer;
|
||||
@@ -642,6 +771,14 @@ FloatBallAppWM.prototype.getMonetAccentForBall = function() {
|
||||
FloatBallAppWM.prototype.updateBallContentBackground = function(contentView) {
|
||||
try {
|
||||
var ballColor = this.getMonetAccentForBall();
|
||||
try {
|
||||
var bgHex = String(this.config.BALL_BG_COLOR_HEX || "").trim();
|
||||
if (bgHex.length > 0) {
|
||||
ballColor = android.graphics.Color.parseColor(bgHex);
|
||||
}
|
||||
} catch(eCustomBg) {
|
||||
safeLog(this.L, 'e', "BALL_BG_COLOR_HEX parse failed, fallback Monet accent: " + String(eCustomBg));
|
||||
}
|
||||
var dark = this.isDarkTheme();
|
||||
var alpha01 = dark ? this.config.BALL_RIPPLE_ALPHA_DARK : this.config.BALL_RIPPLE_ALPHA_LIGHT;
|
||||
var rippleColor = this.withAlpha(ballColor, alpha01);
|
||||
@@ -761,33 +898,49 @@ FloatBallAppWM.prototype.applyTextColorRecursive = function(v, colorInt) {
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.updatePanelBackground = function(panelView) {
|
||||
// 这段代码的主要内容/用途:统一为"主面板/设置面板/查看器面板"应用背景与文字颜色(自动/亮/暗三档),并输出调试日志(命中哪个颜色)。
|
||||
// 这段代码的主要内容/用途:统一为"主面板/设置面板/查看器面板"应用背景与文字颜色;SETTINGS_THEME=animal 时使用动物岛色,monet 时保持系统莫奈。
|
||||
try {
|
||||
var bg = new android.graphics.drawable.GradientDrawable();
|
||||
bg.setCornerRadius(this.dp(22));
|
||||
var isDark = this.isDarkTheme();
|
||||
var settTheme = "animal";
|
||||
try { settTheme = String(this.config.SETTINGS_THEME || "animal"); } catch(eSetTheme) { settTheme = "animal"; }
|
||||
|
||||
var bgInt = this.getPanelBgColorInt();
|
||||
var bgInt = 0;
|
||||
var tc = 0;
|
||||
var stroke = 0;
|
||||
var radiusDp = 22;
|
||||
|
||||
if (settTheme === "animal" && this.getAnimalIslandTheme) {
|
||||
var Color = android.graphics.Color;
|
||||
bgInt = Color.parseColor(isDark ? "#27362E" : "#E4F1DF");
|
||||
tc = Color.parseColor(isDark ? "#F5EBD2" : "#3F3528");
|
||||
stroke = this.withAlpha(Color.parseColor(isDark ? "#526454" : "#E8DEC8"), isDark ? 0.50 : 0.52);
|
||||
radiusDp = 30;
|
||||
} else {
|
||||
bgInt = this.getPanelBgColorInt();
|
||||
tc = this.getPanelTextColorInt(bgInt);
|
||||
var outlineColor = this.ui.colors._monetOutline || (isDark ? android.graphics.Color.parseColor("#8E918F") : android.graphics.Color.parseColor("#747775"));
|
||||
stroke = this.withAlpha(outlineColor, isDark ? 0.26 : 0.20);
|
||||
}
|
||||
|
||||
bg.setCornerRadius(this.dp(radiusDp));
|
||||
bg.setColor(bgInt);
|
||||
|
||||
// 轻量描边:亮色时更明显,暗色时也保留一点边界(不提供自定义输入,避免设置页复杂化)
|
||||
var sw = this.dp(1);
|
||||
var isDark = this.isDarkTheme();
|
||||
var outlineColor = this.ui.colors._monetOutline || (isDark ? android.graphics.Color.parseColor("#8E918F") : android.graphics.Color.parseColor("#747775"));
|
||||
var stroke = this.withAlpha(outlineColor, isDark ? 0.26 : 0.20);
|
||||
try { bg.setStroke(sw, stroke); } catch(eS) { safeLog(null, 'e', "catch " + String(eS)); }
|
||||
|
||||
panelView.setBackground(bg);
|
||||
|
||||
var tc = this.getPanelTextColorInt(bgInt);
|
||||
try { themeBgInt = bgInt; themeTextInt = tc; } catch(eT) { safeLog(null, 'e', "catch " + String(eT)); }
|
||||
this.applyTextColorRecursive(panelView, tc);
|
||||
|
||||
try { _th_log(this.L, "d", "[t]apply bg=" + _th_hex(bgInt) + " tx=" + _th_hex(tc)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
|
||||
|
||||
try { _th_log(this.L, "d", "[t]apply theme=" + settTheme + " bg=" + _th_hex(bgInt) + " tx=" + _th_hex(tc)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
|
||||
|
||||
try {
|
||||
_th_log(this.L, "d",
|
||||
"[theme:apply] isDark=" + isDark +
|
||||
"[theme:apply] theme=" + settTheme +
|
||||
" isDark=" + isDark +
|
||||
" bg=" + _th_hex(bgInt) + " " + _th_argb(bgInt) +
|
||||
" text=" + _th_hex(tc) + " " + _th_argb(tc) +
|
||||
" stroke=" + _th_hex(stroke)
|
||||
|
||||
@@ -153,7 +153,23 @@ FloatBallAppWM.prototype.setPendingValue = function(k, v) {
|
||||
if (!this.state.pendingUserCfg) this.beginEditConfig();
|
||||
this.state.pendingUserCfg[k] = v;
|
||||
this.state.pendingDirty = true;
|
||||
if (this.state.previewMode) {
|
||||
|
||||
// 设置页主题切换:不论 previewMode 都重建设置页 UI
|
||||
if (String(k) === "SETTINGS_THEME") {
|
||||
try {
|
||||
if (this.state.toolAppActive && this.replaceToolAppPage) {
|
||||
this.replaceToolAppPage(String(this.state.toolAppRoute || "settings_group"));
|
||||
} else {
|
||||
if (this.state.settingsPanel) {
|
||||
this.safeRemoveView(this.state.settingsPanel, "settingsPanel");
|
||||
this.state.settingsPanel = null;
|
||||
this.state.settingsPanelLp = null;
|
||||
this.state.addedSettings = false;
|
||||
}
|
||||
this.replaceToolAppPage("settings_group");
|
||||
}
|
||||
} catch(eReb) { safeLog(null, 'e', "catch " + String(eReb)); }
|
||||
} else if (this.state.previewMode) {
|
||||
this.refreshPreview(k);
|
||||
}
|
||||
};
|
||||
@@ -301,7 +317,32 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
|
||||
} catch(eLK) { safeLog(null, 'e', "catch " + String(eLK)); }
|
||||
return;
|
||||
}
|
||||
if (k === "BALL_SIZE_DP" || k === "BALL_PNG_MODE" || k === "BALL_ICON_TYPE" || k === "BALL_ICON_FILE_PATH" || k === "BALL_ICON_RES_ID" || k === "BALL_ICON_RES_NAME" || k === "BALL_ICON_SIZE_DP" || k === "BALL_ICON_TINT_HEX") { this.rebuildBallForNewSize(); return; }
|
||||
if (k === "BALL_SIZE_DP" || k === "BALL_PNG_MODE" || k === "BALL_ICON_TYPE" || k === "BALL_ICON_FILE_PATH" || k === "BALL_ICON_RES_ID" || k === "BALL_ICON_RES_NAME" || k === "BALL_ICON_SIZE_DP" || k === "BALL_ICON_TINT_HEX" || k === "BALL_BG_COLOR_HEX") { this.rebuildBallForNewSize(); return; }
|
||||
|
||||
if (k === "TOOLAPP_BACK_EDGE_WIDTH_DP" || k === "ENABLE_TOOLAPP_INNER_BACK_STRIPS") {
|
||||
try {
|
||||
if (this.state.toolAppActive) {
|
||||
if (k === "ENABLE_TOOLAPP_INNER_BACK_STRIPS" && this.showToolApp) {
|
||||
this.showToolApp(this.state.toolAppRoute || "settings", false);
|
||||
}
|
||||
if (this.refreshToolAppScreenBackStrips) this.refreshToolAppScreenBackStrips();
|
||||
}
|
||||
} catch(eBackStrip) {
|
||||
safeLog(this.L, "w", "apply back strip fail: " + String(eBackStrip));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (k === "ENABLE_TOOLAPP_SCREEN_BACK_STRIPS") {
|
||||
try {
|
||||
if (this.state.toolAppActive && this.refreshToolAppScreenBackStrips) {
|
||||
this.refreshToolAppScreenBackStrips();
|
||||
}
|
||||
} catch(eScreenBackStrip) {
|
||||
safeLog(this.L, "w", "apply screen back strip fail: " + String(eScreenBackStrip));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (k === "PANEL_ROWS" || k === "PANEL_COLS" ||
|
||||
k === "PANEL_ITEM_SIZE_DP" || k === "PANEL_GAP_DP" ||
|
||||
|
||||
@@ -1577,12 +1577,13 @@ FloatBallAppWM.prototype.showIconPicker = function(opts) {
|
||||
if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) {
|
||||
try {
|
||||
var rect = new android.graphics.Rect();
|
||||
if (state.root) {
|
||||
state.root.getGlobalVisibleRect(rect);
|
||||
if (panel) {
|
||||
panel.getGlobalVisibleRect(rect);
|
||||
var x = e.getRawX();
|
||||
var y = e.getRawY();
|
||||
if (!rect.contains(x, y)) {
|
||||
hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch(eOut) { safeLog(null, 'e', "catch " + String(eOut)); }
|
||||
|
||||
@@ -127,6 +127,8 @@ FloatBallAppWM.prototype.playBounce = function(v) {
|
||||
FloatBallAppWM.prototype.safeRemoveView = function(v, whichName) {
|
||||
try {
|
||||
if (!v) return { ok: true, skipped: true };
|
||||
try { if (this.unregisterPanelPredictiveBack) this.unregisterPanelPredictiveBack(v); } catch (eBack) {}
|
||||
try { if (whichName === "viewerPanel" && this.state && String(this.state.viewerPanelType || "") === "tool_app" && this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
|
||||
this.state.wm.removeView(v);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
@@ -182,10 +184,22 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
|
||||
if (!this.state.addedViewer) return;
|
||||
if (!this.state.viewerPanel) return;
|
||||
|
||||
var oldViewerType = String(this.state.viewerPanelType || "");
|
||||
this.safeRemoveView(this.state.viewerPanel, "viewerPanel");
|
||||
this.state.viewerPanel = null;
|
||||
this.state.viewerPanelLp = null;
|
||||
this.state.viewerPanelType = null;
|
||||
if (oldViewerType === "tool_app") {
|
||||
this.state.toolAppRoot = null;
|
||||
this.state.toolAppBody = null;
|
||||
this.state.toolAppContentHost = null;
|
||||
this.state.toolAppBackPreviewView = null;
|
||||
this.state.toolAppBackPreviewRoute = null;
|
||||
this.state.toolAppBackPreviewReady = false;
|
||||
try { if (this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
|
||||
this.state.toolAppTitleView = null;
|
||||
this.state.toolAppBackButton = null;
|
||||
}
|
||||
this.state.addedViewer = false;
|
||||
|
||||
this.hideMask();
|
||||
@@ -205,6 +219,9 @@ FloatBallAppWM.prototype.handlePanelBack = function(which, reason) {
|
||||
|
||||
if (this.state.addedViewer) {
|
||||
var vt = String(this.state.viewerPanelType || w || "viewer");
|
||||
if (vt === "tool_app" && this.state.toolAppActive && this.popToolAppPage) {
|
||||
return this.popToolAppPage(reason || "back_key");
|
||||
}
|
||||
if (vt === "btn_editor") {
|
||||
if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) {
|
||||
this.state.editingButtonIndex = null;
|
||||
@@ -262,6 +279,254 @@ FloatBallAppWM.prototype.handleSystemUiDismiss = function(reason) {
|
||||
return false;
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.hidePanelPredictiveBackIndicator = function() {
|
||||
try {
|
||||
var v = this.state.predictiveBackIndicatorView;
|
||||
if (v && this.state.wm) {
|
||||
try { this.state.wm.removeView(v); } catch (eRm) {}
|
||||
}
|
||||
this.state.predictiveBackIndicatorView = null;
|
||||
this.state.predictiveBackIndicatorLp = null;
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.showPanelPredictiveBackIndicator = function(edge) {
|
||||
try {
|
||||
if (!this.state.wm) return null;
|
||||
var v = this.state.predictiveBackIndicatorView;
|
||||
var lp = this.state.predictiveBackIndicatorLp;
|
||||
var size = this.dp(46);
|
||||
var edgeLeft = Number(edge) !== 1;
|
||||
if (!v) {
|
||||
v = new android.widget.TextView(context);
|
||||
v.setText(edgeLeft ? "‹" : "›");
|
||||
v.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 30);
|
||||
v.setTypeface(null, android.graphics.Typeface.BOLD);
|
||||
v.setGravity(android.view.Gravity.CENTER);
|
||||
v.setTextColor(android.graphics.Color.WHITE);
|
||||
try {
|
||||
var bg = new android.graphics.drawable.GradientDrawable();
|
||||
bg.setShape(android.graphics.drawable.GradientDrawable.OVAL);
|
||||
var c = (this.ui && this.ui.colors && this.ui.colors.primary) ? this.ui.colors.primary : android.graphics.Color.parseColor("#005BC0");
|
||||
bg.setColor(this.withAlpha ? this.withAlpha(c, 0.92) : c);
|
||||
v.setBackground(bg);
|
||||
v.setElevation(this.dp(12));
|
||||
} catch (eBg) {}
|
||||
lp = new android.view.WindowManager.LayoutParams(
|
||||
size,
|
||||
size,
|
||||
android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
|
||||
android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
android.graphics.PixelFormat.TRANSLUCENT
|
||||
);
|
||||
lp.gravity = (edgeLeft ? android.view.Gravity.START : android.view.Gravity.END) | android.view.Gravity.CENTER_VERTICAL;
|
||||
lp.x = this.dp(6);
|
||||
lp.y = 0;
|
||||
v.setAlpha(0);
|
||||
try { this.state.wm.addView(v, lp); } catch (eAdd) { return null; }
|
||||
this.state.predictiveBackIndicatorView = v;
|
||||
this.state.predictiveBackIndicatorLp = lp;
|
||||
} else {
|
||||
try { v.setText(edgeLeft ? "‹" : "›"); } catch (eTxt) {}
|
||||
if (lp) {
|
||||
lp.gravity = (edgeLeft ? android.view.Gravity.START : android.view.Gravity.END) | android.view.Gravity.CENTER_VERTICAL;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'w', "show predictive back indicator fail: " + String(e));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.resetPanelPredictiveBackVisual = function(panel) {
|
||||
try {
|
||||
if (panel && this.state && this.state.toolAppRoot === panel && this.clearToolAppBackPreview) {
|
||||
this.clearToolAppBackPreview(true);
|
||||
this.hidePanelPredictiveBackIndicator();
|
||||
return;
|
||||
}
|
||||
if (panel) {
|
||||
panel.setAlpha(1.0);
|
||||
panel.setTranslationX(0);
|
||||
panel.setScaleX(1.0);
|
||||
panel.setScaleY(1.0);
|
||||
}
|
||||
this.hidePanelPredictiveBackIndicator();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.applyPanelPredictiveBackProgress = function(panel, event) {
|
||||
try {
|
||||
if (!panel || !event) return;
|
||||
var p = 0;
|
||||
try { p = Number(event.getProgress()); } catch (eP) { p = 0; }
|
||||
if (isNaN(p)) p = 0;
|
||||
if (p < 0) p = 0;
|
||||
if (p > 1) p = 1;
|
||||
var edge = 0;
|
||||
try { edge = Number(event.getSwipeEdge()); } catch (eE) { edge = 0; }
|
||||
if (panel && this.state && this.state.toolAppRoot === panel && this.applyToolAppBackPreviewProgress && this.hasToolAppBackTarget && this.hasToolAppBackTarget()) {
|
||||
this.state.toolAppBackEdge = edge;
|
||||
|
||||
var dragPx = 0;
|
||||
try {
|
||||
var panelW = 0;
|
||||
try { panelW = Number(panel.getWidth ? panel.getWidth() : 0); } catch(ePW) { panelW = 0; }
|
||||
if (!panelW || panelW < this.dp(120)) {
|
||||
try { panelW = Number((this.state.viewerPanelLp && this.state.viewerPanelLp.width) || 0); } catch(eLpW) { panelW = 0; }
|
||||
}
|
||||
if (!panelW || panelW < this.dp(120)) panelW = this.dp(320);
|
||||
|
||||
var maxFollow = Math.min(this.dp(220), Math.floor(panelW * 0.45));
|
||||
dragPx = Math.round(maxFollow * p);
|
||||
} catch(eDrag) {
|
||||
dragPx = Math.round(this.dp(180) * p);
|
||||
}
|
||||
|
||||
try {
|
||||
var nowPb = Date.now();
|
||||
if (!this.state.toolAppPredictiveBackLogAt || (nowPb - Number(this.state.toolAppPredictiveBackLogAt || 0)) > 300) {
|
||||
this.state.toolAppPredictiveBackLogAt = nowPb;
|
||||
safeLog(this.L, 'd', 'predictive back progress edge=' + String(edge) + ' p=' + String(p) + ' dragPx=' + String(dragPx));
|
||||
}
|
||||
} catch(eLogPb) {}
|
||||
|
||||
this.applyToolAppBackPreviewProgress(edge, p, dragPx);
|
||||
return;
|
||||
}
|
||||
var dir = edge === 1 ? -1 : 1;
|
||||
panel.setAlpha(1.0 - 0.18 * p);
|
||||
panel.setTranslationX(dir * this.dp(36) * p);
|
||||
var s = 1.0 - 0.025 * p;
|
||||
panel.setScaleX(s);
|
||||
panel.setScaleY(s);
|
||||
|
||||
// overlay 窗口下系统自己的预测性返回箭头在部分 ColorOS 版本不可见,额外绘制一个轻量边缘提示。
|
||||
var ind = this.showPanelPredictiveBackIndicator(edge);
|
||||
if (ind) {
|
||||
ind.setAlpha(Math.min(1.0, 0.20 + 0.80 * p));
|
||||
ind.setScaleX(0.82 + 0.22 * p);
|
||||
ind.setScaleY(0.82 + 0.22 * p);
|
||||
ind.setTranslationX(dir * this.dp(18) * p);
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.unregisterPanelPredictiveBack = function(panel) {
|
||||
try {
|
||||
var entries = this.state.panelBackCallbackEntries || [];
|
||||
var kept = [];
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var it = entries[i];
|
||||
if (!it || it.view === panel) {
|
||||
try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) {}
|
||||
} else {
|
||||
kept.push(it);
|
||||
}
|
||||
}
|
||||
this.state.panelBackCallbackEntries = kept;
|
||||
this.resetPanelPredictiveBackVisual(panel);
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'w', "unregister predictive back fail: " + String(e));
|
||||
}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.registerPanelPredictiveBack = function(panel, which) {
|
||||
// 返回注册优先级:Android 14+ OnBackAnimationCallback(有 progress)→ Android 13 OnBackInvokedCallback(仅最终返回)→ 旧系统 KEYCODE_BACK。
|
||||
try {
|
||||
if (!panel) return false;
|
||||
if (android.os.Build.VERSION.SDK_INT < 33) return false;
|
||||
// attach listener + post fallback may fire almost together; avoid unregister/register churn and duplicate logs.
|
||||
try {
|
||||
var nowReg = Date.now();
|
||||
var entries0 = this.state.panelBackCallbackEntries || [];
|
||||
for (var ei0 = 0; ei0 < entries0.length; ei0++) {
|
||||
var it0 = entries0[ei0];
|
||||
if (it0 && it0.view === panel && String(it0.which || "") === String(which || "") && (nowReg - Number(it0.registeredAt || 0)) < 300) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch(eRegDebounce) {}
|
||||
this.unregisterPanelPredictiveBack(panel);
|
||||
var dispatcher = null;
|
||||
try { dispatcher = panel.findOnBackInvokedDispatcher(); } catch (eFind) { dispatcher = null; }
|
||||
if (!dispatcher) {
|
||||
safeLog(this.L, 'w', "predictive back dispatcher missing which=" + String(which || ""));
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var cb = null;
|
||||
var mode = "none";
|
||||
var usedAnimation = false;
|
||||
|
||||
function finishBack(reason) {
|
||||
if (String(which || "") === "tool_app" && self.finishToolAppBackPreview && self.hasToolAppBackTarget && self.hasToolAppBackTarget()) {
|
||||
var edge = 0;
|
||||
try { edge = Number(self.state.toolAppBackEdge || 0); } catch (eEdge) { edge = 0; }
|
||||
self.finishToolAppBackPreview(edge, true);
|
||||
return;
|
||||
}
|
||||
self.resetPanelPredictiveBackVisual(panel);
|
||||
self.handlePanelBack(which, reason || "predictive_back");
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 34) {
|
||||
try {
|
||||
// 核心:优先创建真正的 android.window.OnBackAnimationCallback。
|
||||
// Class.forName 只用于预热/instanceof 校验;JavaAdapter 必须使用 Packages 下的接口对象。
|
||||
var animCls = java.lang.Class.forName("android.window.OnBackAnimationCallback");
|
||||
cb = new JavaAdapter(Packages.android.window.OnBackAnimationCallback, {
|
||||
onBackStarted: function(event) { self.applyPanelPredictiveBackProgress(panel, event); },
|
||||
onBackProgressed: function(event) { self.applyPanelPredictiveBackProgress(panel, event); },
|
||||
onBackCancelled: function() { self.resetPanelPredictiveBackVisual(panel); },
|
||||
onBackInvoked: function() { finishBack("predictive_back"); }
|
||||
});
|
||||
usedAnimation = !!animCls.isInstance(cb);
|
||||
mode = usedAnimation ? "OnBackAnimationCallback" : "OnBackAnimationCallback-proxy-not-instance";
|
||||
if (!usedAnimation) {
|
||||
safeLog(self.L, 'w', "OnBackAnimationCallback proxy not instance; fallback to final-only callback");
|
||||
cb = null;
|
||||
}
|
||||
} catch (eAnim) {
|
||||
safeLog(self.L, 'w', "create OnBackAnimationCallback fail: " + String(eAnim));
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cb) {
|
||||
try {
|
||||
var cbCls = java.lang.Class.forName("android.window.OnBackInvokedCallback");
|
||||
cb = new JavaAdapter(cbCls, {
|
||||
onBackInvoked: function() { finishBack("on_back_invoked"); }
|
||||
});
|
||||
mode = "OnBackInvokedCallback";
|
||||
} catch (eCb) {
|
||||
safeLog(self.L, 'w', "create OnBackInvokedCallback fail: " + String(eCb));
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
if (!cb) return false;
|
||||
|
||||
var priority = 0;
|
||||
try {
|
||||
// 与规则文件实现保持一致:默认优先级最容易拿到系统 back pipeline;overlay priority 在 ColorOS 上可能只给最终回调。
|
||||
priority = android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
|
||||
} catch (ePri) { priority = 0; }
|
||||
dispatcher.registerOnBackInvokedCallback(priority, cb);
|
||||
if (!this.state.panelBackCallbackEntries) this.state.panelBackCallbackEntries = [];
|
||||
this.state.panelBackCallbackEntries.push({ view: panel, dispatcher: dispatcher, callback: cb, which: String(which || ""), animation: usedAnimation, mode: mode, registeredAt: Date.now() });
|
||||
safeLog(this.L, 'i', "back callback registered which=" + String(which || "") + " mode=" + mode + " priority=" + String(priority));
|
||||
return true;
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'w', "register predictive back fail which=" + String(which || "") + " err=" + String(e));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.attachPanelSystemKeyHandler = function(panel, which) {
|
||||
try {
|
||||
if (!panel) return;
|
||||
@@ -279,7 +544,25 @@ FloatBallAppWM.prototype.attachPanelSystemKeyHandler = function(panel, which) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
panel.post(new java.lang.Runnable({ run: function() { try { panel.requestFocus(); } catch(eFocus) {} } }));
|
||||
var registerAfterAttach = function() {
|
||||
try { panel.requestFocus(); } catch(eFocus) {}
|
||||
try { self.registerPanelPredictiveBack(panel, which); } catch(eBack) {
|
||||
try { safeLog(self.L, 'w', "panel predictive register after attach fail: " + String(eBack)); } catch(eLog) {}
|
||||
}
|
||||
};
|
||||
try {
|
||||
panel.addOnAttachStateChangeListener(new android.view.View.OnAttachStateChangeListener({
|
||||
onViewAttachedToWindow: function(v) {
|
||||
try { v.post(new java.lang.Runnable({ run: registerAfterAttach })); } catch(ePost) { registerAfterAttach(); }
|
||||
},
|
||||
onViewDetachedFromWindow: function(v) {
|
||||
try { self.unregisterPanelPredictiveBack(v); } catch(eUnreg) {}
|
||||
}
|
||||
}));
|
||||
} catch (eAttach) {
|
||||
safeLog(self.L, 'w', "add attach listener fail: " + String(eAttach));
|
||||
}
|
||||
panel.post(new java.lang.Runnable({ run: registerAfterAttach }));
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'e', "attachPanelSystemKeyHandler fail which=" + String(which || "") + " err=" + String(e));
|
||||
}
|
||||
@@ -325,9 +608,14 @@ FloatBallAppWM.prototype._clearHeavyCachesIfAllHidden = function(reason) {
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.hideAllPanels = function() {
|
||||
try { if (this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
|
||||
this.hideMainPanel();
|
||||
this.hideSettingsPanel();
|
||||
this.hideViewerPanel();
|
||||
this.state.toolAppActive = false;
|
||||
this.state.toolAppRoute = null;
|
||||
this.state.toolAppNavStack = [];
|
||||
this.state.settingsGroupKey = null;
|
||||
this.hideMask();
|
||||
|
||||
this._clearHeavyCachesIfAllHidden("hideAllPanels");
|
||||
@@ -397,6 +685,13 @@ FloatBallAppWM.prototype.snapToEdgeDocked = function(withAnim, forceSide) {
|
||||
// 如果需要保护,调用方自己判断
|
||||
if (this.state.dragging) return;
|
||||
|
||||
try {
|
||||
var freshScreen = this.getScreenSizePx();
|
||||
if (freshScreen && freshScreen.w > 0 && freshScreen.h > 0) {
|
||||
this.state.screen = freshScreen;
|
||||
}
|
||||
} catch (eScreen) {}
|
||||
|
||||
var di = this.getDockInfo();
|
||||
var ballSize = di.ballSize;
|
||||
var visible = di.visiblePx;
|
||||
@@ -629,12 +924,10 @@ FloatBallAppWM.prototype.safeUiCall = function(tag, fn) {
|
||||
|
||||
;
|
||||
|
||||
FloatBallAppWM.prototype.onScreenChangedReflow = function() {
|
||||
FloatBallAppWM.prototype.onScreenChangedReflow = function(reason) {
|
||||
if (this.state.closing) return;
|
||||
if (!this.state.addedBall) return;
|
||||
|
||||
var di = this.getDockInfo();
|
||||
|
||||
var oldW = this.state.screen.w;
|
||||
var oldH = this.state.screen.h;
|
||||
|
||||
@@ -643,11 +936,23 @@ FloatBallAppWM.prototype.onScreenChangedReflow = function() {
|
||||
var newH = newScreen.h;
|
||||
|
||||
if (newW <= 0 || newH <= 0) return;
|
||||
var rotNow = -1;
|
||||
try { rotNow = this.getRotation ? this.getRotation() : -1; } catch (eRotNow) { rotNow = -1; }
|
||||
try {
|
||||
var rotLandscape = (rotNow === android.view.Surface.ROTATION_90 || rotNow === android.view.Surface.ROTATION_270);
|
||||
var rotPortrait = (rotNow === android.view.Surface.ROTATION_0 || rotNow === android.view.Surface.ROTATION_180);
|
||||
if ((rotLandscape && newW < newH) || (rotPortrait && newW > newH)) {
|
||||
safeLog(this.L, 'w', "screen reflow skip unstable size reason=" + String(reason || "") + " new=" + newW + "x" + newH + " rot=" + String(rotNow));
|
||||
return;
|
||||
}
|
||||
} catch (eStable) {}
|
||||
if (oldW <= 0) oldW = newW;
|
||||
if (oldH <= 0) oldH = newH;
|
||||
|
||||
this.state.screen = { w: newW, h: newH };
|
||||
|
||||
var di = this.getDockInfo();
|
||||
|
||||
var ballSize = di.ballSize;
|
||||
var visible = di.visiblePx;
|
||||
var hidden = di.hiddenPx;
|
||||
@@ -690,7 +995,45 @@ FloatBallAppWM.prototype.onScreenChangedReflow = function() {
|
||||
try { this.state.wm.updateViewLayout(this.state.ballRoot, this.state.ballLp); } catch(eU) { safeLog(null, 'e', "catch " + String(eU)); }
|
||||
this.savePos(this.state.ballLp.x, this.state.ballLp.y);
|
||||
|
||||
safeLog(this.L, 'i', "screen reflow w=" + String(newW) + " h=" + String(newH) + " x=" + String(this.state.ballLp.x) + " y=" + String(this.state.ballLp.y));
|
||||
if (this.state.toolAppActive && this.refreshToolAppScreenBackStrips) {
|
||||
try { this.refreshToolAppScreenBackStrips(); } catch(eBackStrip) { safeLog(this.L, 'w', "screen reflow refresh tool app back strips fail: " + String(eBackStrip)); }
|
||||
}
|
||||
|
||||
safeLog(this.L, 'i',
|
||||
"screen reflow reason=" + String(reason || "") +
|
||||
" old=" + oldW + "x" + oldH +
|
||||
" new=" + newW + "x" + newH +
|
||||
" rot=" + String(rotNow) +
|
||||
" docked=" + String(this.state.docked) +
|
||||
" side=" + String(this.state.dockSide || "") +
|
||||
" x=" + String(this.state.ballLp.x) +
|
||||
" y=" + String(this.state.ballLp.y)
|
||||
);
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.scheduleScreenReflow = function(reason) {
|
||||
try {
|
||||
var self = this;
|
||||
if (!this.state.h) {
|
||||
this.onScreenChangedReflow(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onScreenChangedReflow(reason);
|
||||
|
||||
this.state.h.postDelayed(new JavaAdapter(java.lang.Runnable, {
|
||||
run: function() {
|
||||
try {
|
||||
if (self.state.closing) return;
|
||||
self.onScreenChangedReflow(String(reason || "") + ":delayed");
|
||||
} catch (e) {
|
||||
safeLog(self.L, "w", "delayed screen reflow fail reason=" + String(reason || "") + " err=" + String(e));
|
||||
}
|
||||
}
|
||||
}), 260);
|
||||
} catch (e0) {
|
||||
try { this.onScreenChangedReflow(reason); } catch(e1) {}
|
||||
}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.setupDisplayMonitor = function() {
|
||||
@@ -730,7 +1073,7 @@ FloatBallAppWM.prototype.setupDisplayMonitor = function() {
|
||||
|
||||
if (changed) {
|
||||
self.cancelDockTimer();
|
||||
self.onScreenChangedReflow();
|
||||
self.scheduleScreenReflow("display_changed");
|
||||
self.touchActivity();
|
||||
}
|
||||
} catch (e1) {
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
FloatBallAppWM.prototype.createSectionHeader = function(item, parent) {
|
||||
var isDark = this.isDarkTheme();
|
||||
var C = this.ui.colors;
|
||||
var color = C.primary;
|
||||
var T = this.getAnimalIslandTheme ? this.getAnimalIslandTheme() : null;
|
||||
try { if (this.applySettingsTheme && T) this.applySettingsTheme(T, isDark, C, this.state.pendingUserCfg || this.config); } catch(eTheme) { safeLog(null, 'e', "catch " + String(eTheme)); }
|
||||
var color = T ? T.primary : C.primary;
|
||||
|
||||
var h = new android.widget.TextView(context);
|
||||
h.setText(String(item.name || ""));
|
||||
@@ -17,11 +19,14 @@ FloatBallAppWM.prototype.createSectionHeader = function(item, parent) {
|
||||
FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivider) {
|
||||
var isDark = this.isDarkTheme();
|
||||
var C = this.ui.colors;
|
||||
var textColor = isDark ? C.textPriDark : C.textPriLight;
|
||||
var secColor = isDark ? C.textSecDark : C.textSecLight;
|
||||
var dividerColor = isDark ? C.dividerDark : C.dividerLight;
|
||||
var primary = C.primary;
|
||||
var switchOff = isDark ? (0xFF555555 | 0) : (0xFFCCCCCC | 0);
|
||||
var T = this.getAnimalIslandTheme ? this.getAnimalIslandTheme() : null;
|
||||
try { if (this.applySettingsTheme && T) this.applySettingsTheme(T, isDark, C, this.state.pendingUserCfg || this.config); } catch(eTheme) { safeLog(null, 'e', "catch " + String(eTheme)); }
|
||||
var textColor = T ? T.text : (isDark ? C.textPriDark : C.textPriLight);
|
||||
var secColor = T ? T.sub : (isDark ? C.textSecDark : C.textSecLight);
|
||||
var dividerColor = T ? T.stroke : (isDark ? C.dividerDark : C.dividerLight);
|
||||
var primary = T ? T.primary : C.primary;
|
||||
var inputBgColor = T ? T.card2 : (isDark ? C.inputBgDark : C.inputBgLight);
|
||||
var switchOff = T ? T.card2 : (isDark ? (0xFF555555 | 0) : (0xFFCCCCCC | 0));
|
||||
|
||||
// 增加内边距
|
||||
var padH = this.dp(16);
|
||||
@@ -232,14 +237,46 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
// 透明波纹背景
|
||||
btn.setBackground(self.ui.createTransparentRippleDrawable(primary, self.dp(16)));
|
||||
|
||||
btn.setOnClickListener(new android.view.View.OnClickListener({
|
||||
onClick: function(v) {
|
||||
function runSettingAction() {
|
||||
try {
|
||||
self.touchActivity();
|
||||
if (item.action === "open_btn_mgr") {
|
||||
var action = String(item.action || "");
|
||||
if (action === "open_btn_mgr") {
|
||||
self.showPanelAvoidBall("btn_editor");
|
||||
return;
|
||||
}
|
||||
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
|
||||
if (action === "open_schema_editor") {
|
||||
self.showPanelAvoidBall("schema_editor");
|
||||
return;
|
||||
}
|
||||
if (action === "open_settings") {
|
||||
self.showPanelAvoidBall("settings");
|
||||
return;
|
||||
}
|
||||
if (action === "open_manual" || action === "open_doc") {
|
||||
var intent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
|
||||
intent.setData(android.net.Uri.parse("https://xin-blog.com/114.html"));
|
||||
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
if (action && (action.indexOf("http://") === 0 || action.indexOf("https://") === 0)) {
|
||||
var urlIntent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
|
||||
urlIntent.setData(android.net.Uri.parse(action));
|
||||
urlIntent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(urlIntent);
|
||||
return;
|
||||
}
|
||||
self.toast(action ? ("暂不支持动作: " + action) : "动作未配置");
|
||||
} catch(e) {
|
||||
try { self.toast("动作执行失败: " + String(e)); } catch(eToast) {}
|
||||
safeLog(null, 'e', "setting action fail " + String(e));
|
||||
}
|
||||
}
|
||||
|
||||
btn.setOnClickListener(new android.view.View.OnClickListener({
|
||||
onClick: function(v) {
|
||||
runSettingAction();
|
||||
}
|
||||
}));
|
||||
row.addView(btn);
|
||||
@@ -247,12 +284,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
// 行点击也触发
|
||||
row.setOnClickListener(new android.view.View.OnClickListener({
|
||||
onClick: function(v) {
|
||||
try {
|
||||
self.touchActivity();
|
||||
if (item.action === "open_btn_mgr") {
|
||||
self.showPanelAvoidBall("btn_editor");
|
||||
}
|
||||
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
|
||||
runSettingAction();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -271,7 +303,8 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
et.setText(String(curVal));
|
||||
et.setTextColor(textColor);
|
||||
et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
et.setBackground(self.ui.createRoundDrawable(isDark ? C.inputBgDark : C.inputBgLight, self.dp(6)));
|
||||
try { et.setHintTextColor(secColor); } catch(eHint) { safeLog(null, 'e', "catch " + String(eHint)); }
|
||||
et.setBackground(self.ui.createStrokeDrawable(inputBgColor, dividerColor, self.dp(1), self.dp(8)));
|
||||
et.setPadding(self.dp(8), self.dp(8), self.dp(8), self.dp(8));
|
||||
et.setSingleLine(true);
|
||||
|
||||
@@ -407,7 +440,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
}
|
||||
refreshBallShortXPreview();
|
||||
|
||||
var btnPick = self.ui.createFlatButton(self, "选择图标", primary, function() {
|
||||
var btnPick = self.ui.createFlatButton(self, "换一个", primary, function() {
|
||||
self.touchActivity();
|
||||
self.showShortXIconPickerPopup({
|
||||
currentName: String(self.getPendingValue(item.key) || ""),
|
||||
@@ -430,7 +463,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
gapView.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
|
||||
iconRow.addView(gapView);
|
||||
|
||||
var btnClear = self.ui.createFlatButton(self, "清空", secColor, function() {
|
||||
var btnClear = self.ui.createFlatButton(self, "不用图标", secColor, function() {
|
||||
self.touchActivity();
|
||||
try {
|
||||
self.setPendingValue(item.key, "");
|
||||
@@ -484,7 +517,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
}
|
||||
refreshBallColorPreview();
|
||||
|
||||
var btnColor = self.ui.createFlatButton(self, "选择颜色", primary, function() {
|
||||
var btnColor = self.ui.createFlatButton(self, "换颜色", primary, function() {
|
||||
self.touchActivity();
|
||||
self.showColorPickerPopup({
|
||||
currentColor: String(self.getPendingValue(item.key) || ""),
|
||||
@@ -505,7 +538,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
|
||||
gapColorView.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
|
||||
colorRow.addView(gapColorView);
|
||||
|
||||
var btnClearColor = self.ui.createFlatButton(self, "清空", secColor, function() {
|
||||
var btnClearColor = self.ui.createFlatButton(self, "恢复默认", secColor, function() {
|
||||
self.touchActivity();
|
||||
try {
|
||||
self.setPendingValue(item.key, "");
|
||||
|
||||
2436
code/th_14_panels.js
2436
code/th_14_panels.js
File diff suppressed because it is too large
Load Diff
1201
code/th_15_extra.js
1201
code/th_15_extra.js
File diff suppressed because it is too large
Load Diff
@@ -229,7 +229,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
|
||||
|
||||
if (act === android.content.Intent.ACTION_CONFIGURATION_CHANGED) {
|
||||
self.cancelDockTimer();
|
||||
self.onScreenChangedReflow();
|
||||
self.scheduleScreenReflow("configuration_changed");
|
||||
self.touchActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,40 +2,40 @@
|
||||
"alg": "SHA256withRSA",
|
||||
"files": {
|
||||
"th_01_base.js": {
|
||||
"sha256": "30c121902ef5a0006a5daabca8443ed6cbf6e2508209604c48f89855d0690034",
|
||||
"size": 52546
|
||||
"sha256": "f228e3fda85ab9946934a9950515556d75df110da88432b339258036b8f3a3e2",
|
||||
"size": 57166
|
||||
},
|
||||
"th_02_core.js": {
|
||||
"sha256": "f44f88f0ce3f44f0d1675e55a9f0b66d831caa7819dda54aef6e308ec27faaeb",
|
||||
"size": 3934
|
||||
"sha256": "3c5c498d200e961d48fc9ca3f885475e770ecb32b83ec6a89d23df3f88aed1c9",
|
||||
"size": 4664
|
||||
},
|
||||
"th_03_icon.js": {
|
||||
"sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987",
|
||||
"size": 5598
|
||||
},
|
||||
"th_04_theme.js": {
|
||||
"sha256": "b815acfee30e56458e61c033ab3fddfe5d8d0c52ec172c419bad7187fcb341ba",
|
||||
"size": 36083
|
||||
"sha256": "16a7121c734fccae17b9f8a23c1f0758d3a765541d1de4d6159747fc92bc26a4",
|
||||
"size": 42568
|
||||
},
|
||||
"th_05_persistence.js": {
|
||||
"sha256": "d80787c2810839ebbe499e93db3df33d6e8d2d6b6ae71644ce351db0f36e4d3e",
|
||||
"size": 14077
|
||||
"sha256": "1bec9f5bda8e5ae82f2a42707c59e72f4819fffc200d74ae55f191a76009af9b",
|
||||
"size": 15754
|
||||
},
|
||||
"th_06_icon_parser.js": {
|
||||
"sha256": "25b95a5df634a7ee359f3ab798e4d3154a71c24016f7b4bf8a658096644b2484",
|
||||
"size": 22909
|
||||
},
|
||||
"th_07_shortcut.js": {
|
||||
"sha256": "7b2dbd1e35c636cca4ccce335dfb9e0b972342972ce012116ff4bbcfc438caa1",
|
||||
"size": 72992
|
||||
"sha256": "8e70adda0d73930e2afc1eba488e95016fc2f739ba199834a30bec015fa6156c",
|
||||
"size": 73011
|
||||
},
|
||||
"th_08_content.js": {
|
||||
"sha256": "8a76f15dfd1292081cba4b2dd218424be66540350e2807065421a6176a86c2db",
|
||||
"size": 7938
|
||||
},
|
||||
"th_09_animation.js": {
|
||||
"sha256": "7120d208910955a2a163c4ad535b2eca674f7a0c2c462ef2f03bad11c9511933",
|
||||
"size": 27541
|
||||
"sha256": "2570ffbbc33fa2318e27c5e99c4e78e1bf96c7da8ec134053acda009207488cb",
|
||||
"size": 42007
|
||||
},
|
||||
"th_10_shell.js": {
|
||||
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
|
||||
@@ -50,23 +50,23 @@
|
||||
"size": 2362
|
||||
},
|
||||
"th_13_panel_ui.js": {
|
||||
"sha256": "19e5e1c346051aafdda6253ed7c25ef5fbc4f883de66f656c9575d97e9d6ffc8",
|
||||
"size": 20386
|
||||
"sha256": "9052e01ccdc0af29615ef8e72550d7d312240f9ab36ebfb66e673c92ab2e4b9c",
|
||||
"size": 22308
|
||||
},
|
||||
"th_14_panels.js": {
|
||||
"sha256": "cf18cb06a9e221b671360f94040542c1c27e8776bb5037abe4f4b0f3de3e1073",
|
||||
"size": 217347
|
||||
"sha256": "4b73af93ff5b8fa8ffadf76f936732b93a32ccff8a6623b96f9ad3bbb8114ec8",
|
||||
"size": 304993
|
||||
},
|
||||
"th_15_extra.js": {
|
||||
"sha256": "ed56b19a5a798a785c024eb931140ded69d16921d2e87ad4ccd861df1c1907d8",
|
||||
"size": 62936
|
||||
"sha256": "fb8349a61771903a8c2a3ca6fee15f8df88af79b684e7b444b35c38bcf354ed5",
|
||||
"size": 117741
|
||||
},
|
||||
"th_16_entry.js": {
|
||||
"sha256": "e7c99c3dfbd6aedab05551426955081ae6cae034754f2f557cefa01dc75dc001",
|
||||
"size": 12777
|
||||
"sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1",
|
||||
"size": 12799
|
||||
}
|
||||
},
|
||||
"keyId": "toolhub-targets-2026-rsa3072",
|
||||
"schema": 2,
|
||||
"version": 20260512022403
|
||||
"version": 20260521235402
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
mNKpW6JxqzeRVFYnEjJcxIogH2bMLbR5rExVdGHMQcOXhtAbamkl6ZLEtUG534wweU95Amku8lxWO+Pe5p2Bg7ufExHqtBgm3eFsDtftGlG7KYOakPvCluGcWZivGMrxFdUq4CsF/Y4dhi9UG9Dtr2X8UdnKQRXLIMtPW+EpDvZvheg4G+7hsEKOYyRzkX1HK8/KEUaaiDogr7Hp+rT0jPa0iCMBn3fiLAIODYMWj0mzjML7WqtBOwXBoLGSeHxJ7BwJd1USxQh+pMo0/jxf/Uas7oTue67bYpyub9v2ESAfiLIg+qijP9oQRnptvtSA99c0Qj/8BJLiXIl6AD1Qp+PhnJwmGgQg62OtJdp7ly0zPz51UbULXfPwqj9djguac1yN7qVGQroT2oo93brZMpV3iRwQw2ov2E/efFf4iXSXnd/aMbzozUNQtqINnsWfZsrjBNxuDLLHvuSUh5h19/Yok+5EczPL5iZDl/W1GNmurrDvOhnTCZjuVS7t97Zt
|
||||
Ho5g8n2gFgHoMdeo0wpzG4b3ufGTSs7Ht1MbGOGU0h0r/u3QRAJKJTCf1JPmSb6mWCOdM8ZL+LD0u8dfpJd9f4AdIJm+PNHH60YGa6QFEAuzh13eh1wQrd2S/gTSQGMSFqQPKRhdMzuTTBJEgMGK0S3siZP6RPK0hdocs5EpFnAtNd6HZWglcds1FVHMCwYiT1JCffRl+azDDaYlo5JqfdPz6hxfQkw+MEY5hQJ0veSbpuv5wNWRZ1OaPgIdwu7BMnGjbCR8BbYylXBkbB0K2K8FKeLDvuPY9je64xSgN1+dJpV1QTr+52BwxDH53u0L7ZVMYOJwOpdtLNhHNVjKJFJn3vcn2jS0DrX6uP9w/qI9X7Ss2bn8Xf/bkaMj6UpOOB+arkme8P2bM+ziGA/CL2n7SLsicvbUv3eNWRsjwTw9EIUx/sEqo6tr+m88eorZo7C3Idy5gdCtVjSSN3kef1XbHtOQshBZwXYpmFYPtRS0WxqEF7F/desj7k7ztnHv
|
||||
|
||||
29
scripts/verify_button_editor_layout.py
Normal file
29
scripts/verify_button_editor_layout.py
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(__file__).resolve().parents[1]
|
||||
text = (root / "code" / "th_14_panels.js").read_text(encoding="utf-8")
|
||||
checks = [
|
||||
("manager has action chip helper for search", "createButtonManagerActionChip" in text),
|
||||
("manager has text action helper for card actions", "createButtonManagerTextAction" in text),
|
||||
("manager card has two-row layout marker", "按钮管理列表卡片:上信息、下操作" in text),
|
||||
("manager has search surface marker", "按钮管理搜索卡片" in text),
|
||||
("manager no false long-press sort text", "长按卡片排序" not in text),
|
||||
("manager no homepage block text", "按钮管理首页" not in text),
|
||||
("manager header count only", '共 " + buttons.length + " 个按钮' in text or '共 " + buttons.length + " 个工具伙伴' in text),
|
||||
("manager list footer uses remaining-height scroll", "避免“取消更改/保存所有”被挤到屏幕外" in text and "scrollLp.weight = 1" in text),
|
||||
("manager list footer has equal buttons and bottom gap", "btnListCancelLp.weight = 1" in text and "btnListSaveLp.weight = 1" in text and "listBottomLp.setMargins(0, self.dp(6), 0, self.dp(12))" in text),
|
||||
("editor no useless workbench", "按钮编辑工作台" not in text and "createButtonEditorHeroCard" not in text),
|
||||
("editor has field spacing helper", "addButtonEditorField" in text),
|
||||
("editor fixed footer has equal buttons", "btnCancelLp.weight = 1" in text and "btnSaveLp.weight = 1" in text and "self.dp(44)" in text),
|
||||
("editor section descriptions are concise", "按钮名称" in text and "图标和颜色" in text and "点击后做什么" in text and "先填写名称,便于在按钮管理列表中识别" not in text and "选择点击后执行的动作类型与参数" not in text),
|
||||
]
|
||||
failed = [name for name, ok in checks if not ok]
|
||||
if failed:
|
||||
print("Button manager/editor layout verification FAILED:")
|
||||
for name in failed:
|
||||
print(" - " + name)
|
||||
sys.exit(1)
|
||||
print("Button manager/editor layout verification OK")
|
||||
35
scripts/verify_toolapp_adaptive_size.py
Normal file
35
scripts/verify_toolapp_adaptive_size.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(__file__).resolve().parents[1]
|
||||
extra = (root / "code" / "th_15_extra.js").read_text(encoding="utf-8")
|
||||
|
||||
checks = []
|
||||
|
||||
def check(name, ok):
|
||||
checks.append((name, bool(ok)))
|
||||
|
||||
check("has calculateToolAppLayout", "FloatBallAppWM.prototype.calculateToolAppLayout" in extra)
|
||||
check("layout uses screen orientation", "isLandscape" in extra and "shortSide" in extra and "longSide" in extra)
|
||||
check("layout has tiny screen branch", "shortSide < this.dp(420)" in extra)
|
||||
check("layout has tablet/large screen branch", "shortSide >= this.dp(720)" in extra)
|
||||
check("layout applies margins", "marginX" in extra and "marginTop" in extra and "marginBottom" in extra)
|
||||
|
||||
show_match = re.search(r"FloatBallAppWM\.prototype\.showToolApp\s*=\s*function\([^)]*\)\s*\{(?P<body>.*?)\n\};", extra, re.S)
|
||||
show_body = show_match.group("body") if show_match else ""
|
||||
check("showToolApp exists", bool(show_match))
|
||||
check("showToolApp uses calculateToolAppLayout", "this.calculateToolAppLayout" in show_body)
|
||||
check("showToolApp applies adaptive width", "layout.width" in show_body and "layout.height" in show_body)
|
||||
check("showToolApp updates x/y when reusing root", "viewerPanelLp.x = layout.x" in show_body and "viewerPanelLp.y = layout.y" in show_body)
|
||||
check("showToolApp addPanel uses adaptive x/y", "this.addPanel(shell, layout.x, layout.y" in show_body)
|
||||
check("showToolApp no fixed 0.92/0.82 sizing", "0.92" not in show_body and "0.82" not in show_body)
|
||||
|
||||
failed = [name for name, ok in checks if not ok]
|
||||
if failed:
|
||||
print("ToolApp adaptive-size verification FAILED:")
|
||||
for name in failed:
|
||||
print(" - " + name)
|
||||
sys.exit(1)
|
||||
print("ToolApp adaptive-size verification OK")
|
||||
37
scripts/verify_toolapp_single_root.py
Normal file
37
scripts/verify_toolapp_single_root.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(__file__).resolve().parents[1]
|
||||
core = (root / "code" / "th_02_core.js").read_text(encoding="utf-8")
|
||||
extra = (root / "code" / "th_15_extra.js").read_text(encoding="utf-8")
|
||||
|
||||
checks = []
|
||||
|
||||
def check(name, ok):
|
||||
checks.append((name, bool(ok)))
|
||||
|
||||
check("state keeps a single ToolApp root view", "toolAppRoot" in core)
|
||||
check("state keeps ToolApp content host", "toolAppContentHost" in core)
|
||||
check("state keeps ToolApp title view", "toolAppTitleView" in core)
|
||||
check("state keeps ToolApp back button", "toolAppBackButton" in core)
|
||||
check("has ensureToolAppShell", "FloatBallAppWM.prototype.ensureToolAppShell" in extra)
|
||||
check("has updateToolAppShellChrome", "FloatBallAppWM.prototype.updateToolAppShellChrome" in extra)
|
||||
check("has setToolAppContent", "FloatBallAppWM.prototype.setToolAppContent" in extra)
|
||||
|
||||
show_match = re.search(r"FloatBallAppWM\.prototype\.showToolApp\s*=\s*function\([^)]*\)\s*\{(?P<body>.*?)\n\};", extra, re.S)
|
||||
show_body = show_match.group("body") if show_match else ""
|
||||
check("showToolApp exists", bool(show_match))
|
||||
check("showToolApp does not remove viewer on page switch", "this.hideViewerPanel();" not in show_body)
|
||||
check("showToolApp ensures shell once", "this.ensureToolAppShell" in show_body)
|
||||
check("showToolApp swaps content host", "this.setToolAppContent" in show_body)
|
||||
check("showToolApp only addPanel when shell not added", "!this.state.addedViewer" in show_body and "this.addPanel" in show_body)
|
||||
|
||||
failed = [name for name, ok in checks if not ok]
|
||||
if failed:
|
||||
print("ToolApp single-root verification FAILED:")
|
||||
for name in failed:
|
||||
print(" - " + name)
|
||||
sys.exit(1)
|
||||
print("ToolApp single-root verification OK")
|
||||
Reference in New Issue
Block a user