Compare commits

..

99 Commits

Author SHA1 Message Date
Hermes
fd56e14fc8 Fix ToolApp edge back touch interception 2026-05-22 07:54:19 +08:00
Hermes
557950f692 Fix ToolApp back preview snapshots 2026-05-22 06:54:34 +08:00
Hermes
855352bfd9 Fix ToolApp edge back preview layers 2026-05-22 06:41:54 +08:00
Hermes
454090248d Move ToolApp root during edge back drag 2026-05-22 06:29:54 +08:00
Hermes
25b9f5c52b Fix ToolApp back preview drag fallback 2026-05-22 06:22:57 +08:00
Hermes
67fad1d7f8 Fix ToolApp edge back drag progress 2026-05-22 06:14:44 +08:00
Hermes
dcd3fcb9ee Fix ToolApp screen edge drag follow 2026-05-22 06:00:15 +08:00
Hermes
be2156a634 Fix ToolApp edge back strip defaults 2026-05-22 05:52:12 +08:00
Hermes
a5bd0a5b15 fix: restore ToolApp edge back gesture toggles 2026-05-22 05:39:58 +08:00
7015725
858b1c5f54 fix: debounce panel back callback registration 2026-05-22 05:29:30 +08:00
7015725
c52598bd44 fix: show toolhub editor feedback inline 2026-05-22 05:25:41 +08:00
7015725
195e61d810 fix button editor validation feedback 2026-05-22 05:15:54 +08:00
7015725
544494df8c fix: isolate UI button click guards 2026-05-22 05:06:49 +08:00
7015725
eaeeba2b0a Fix ToolApp edge strips blocking buttons 2026-05-22 04:59:50 +08:00
7015725
3511b3f1ce Fix inert ToolApp UI actions 2026-05-22 04:40:56 +08:00
7015725
32fe099fab fix: make ToolHub UI actions responsive 2026-05-22 04:27:45 +08:00
7015725
78218ce1f5 fix: reduce color picker popup max height 2026-05-22 04:10:53 +08:00
7015725
854e80de80 fix: use valid color picker scroll layout params 2026-05-22 03:59:36 +08:00
7015725
2a97a7a790 fix: make color picker popup responsive 2026-05-22 03:57:31 +08:00
7015725
cc23da6904 fix: increase color picker action button size 2026-05-22 03:50:00 +08:00
7015725
ad58737c67 fix: enlarge color picker actions to match settings 2026-05-22 03:47:39 +08:00
7015725
9c3b628a83 fix: soften color picker action colors 2026-05-22 03:43:51 +08:00
7015725
9ed8b39d0b fix: match color picker actions to settings style 2026-05-22 03:38:59 +08:00
7015725
04818a909e fix: refine color picker action sizing 2026-05-22 03:32:05 +08:00
7015725
2cd1fdd660 fix: restyle color picker action buttons 2026-05-22 03:30:09 +08:00
7015725
3069e4ec4d fix: align color picker bottom actions 2026-05-22 03:26:51 +08:00
7015725
c15869e319 fix: restore stable color picker overlay 2026-05-22 03:24:12 +08:00
7015725
451cfb6502 fix: restore color picker and top bar clicks 2026-05-22 03:15:34 +08:00
7015725
7af30639d1 fix: repair color picker overlay and back preview 2026-05-22 03:06:32 +08:00
7015725
86dca84a27 fix: improve ToolApp landscape edge back handling 2026-05-22 01:34:08 +08:00
7015725
66fa86b37d feat: keep settings detail in tablet landscape panes 2026-05-22 01:20:32 +08:00
7015725
0605201b97 Add settings master detail landscape layout 2026-05-22 01:00:46 +08:00
7015725
918c36ebc2 Optimize ToolHub settings responsive layout 2026-05-22 00:43:33 +08:00
7015725
a6b0e1b41b Revert "refactor: split picker module and improve diagnostics"
This reverts commit b36af7f78a.
2026-05-22 00:11:08 +08:00
7015725
b36af7f78a refactor: split picker module and improve diagnostics 2026-05-21 23:46:33 +08:00
7015725
2643bf9cdf docs: update ToolHub README 2026-05-19 05:24:23 +08:00
7015725
eeac0baa88 Add update source switch 2026-05-19 05:13:06 +08:00
7015725
a6b5736f9a Fix float ball reflow in landscape 2026-05-19 05:06:27 +08:00
7015725
d557bdefa2 fix: lighten animal island main panel 2026-05-19 03:37:25 +08:00
7015725
4ce1924299 style: align main panel with animal island theme 2026-05-19 03:24:24 +08:00
7015725
63fdfb813e Fix long-press move jitter closing settings 2026-05-19 03:02:52 +08:00
7015725
6d8f50267c fix toolapp inner back edge width setting 2026-05-19 01:02:41 +08:00
7015725
eda34d24b1 fix: refresh stale ToolHub settings schema 2026-05-19 00:45:59 +08:00
7015725
3479cb25f2 feat: expand ToolApp back gesture ranges 2026-05-19 00:36:53 +08:00
7015725
1a7cefc630 feat: add ToolHub badge and back gesture settings 2026-05-19 00:26:11 +08:00
7015725
4c054dd4a3 feat: add floating ball background color setting 2026-05-19 00:10:25 +08:00
7015725
49ecd1e74e fix: align ToolApp back preview UI 2026-05-18 23:54:29 +08:00
7015725
e60d4e1b42 Increase ToolApp edge back zone to 48dp 2026-05-18 23:26:06 +08:00
7015725
ae8abc2908 fix: refine island settings bottom layout 2026-05-18 21:38:21 +08:00
7015725
df6f548c76 feat: add icon picker favorites 2026-05-18 14:50:37 +08:00
7015725
3492feb944 feat: optimize island icon picker layout 2026-05-18 14:42:18 +08:00
7015725
25ea8b7837 feat: unify picker island style 2026-05-18 14:28:59 +08:00
7015725
2e3ff489a0 feat: refine island settings UI 2026-05-18 14:20:29 +08:00
7015725
c7705348b4 feat: show settings save status 2026-05-18 14:03:36 +08:00
7015725
eff53e4156 feat: material you settings monet theme 2026-05-18 13:54:24 +08:00
7015725
342171e2ac fix: adapt settings pages to Monet theme 2026-05-18 03:49:54 +08:00
7015725
61a6bf1e72 fix: 按钮管理/按钮编辑等所有设置页 UI 元素跟随 SETTINGS_THEME
之前只覆盖了设置首页和分组页的配色切换,但按钮管理页内的
统计卡片、操作 chip、分区卡片、折叠分组、编辑面板等 UI
组件仍然直接硬编码 getAnimalIslandTheme()。

补全遗漏的 6 个函数:
- createButtonManagerSummaryCard
- createButtonManagerActionChip
- createButtonManagerPolishedCard
- createButtonEditorSectionCard
- createButtonEditorCollapsibleSection
- buildButtonEditorPanelView
2026-05-15 02:31:39 +08:00
7015725
efdcd3f187 fix: 设置页所有 UI 元素统一跟随 SETTINGS_THEME 切换
之前只有 buildSettingsGroupPanelView(分组页)检查了
SETTINGS_THEME,但首页面板 buildSettingsHomePanelView
和入口卡片 createSettingsHomeEntry 直接硬编码
getAnimalIslandTheme(),导致切换到 Monet 后首页仍是
动物岛风。

修复: 提取公共方法 applySettingsTheme(T, isDark, C, cfgTpl),
在三个构建位置统一调用:
- createSettingsHomeEntry (入口卡片:徽章/标题/按钮颜色)
- buildSettingsHomePanelView (设置首页:背景/统计卡/按钮)
- buildSettingsGroupPanelView (分组页:背景/卡片/预览开关)
2026-05-15 02:29:03 +08:00
7015725
b79961b08e fix: 设置页主题切换不再依赖预览模式开关
之前 SETTINGS_THEME 切换只在 previewMode=true 时重建
设置页 UI,用户必须手动打开边调边看才能看到变化。

改为: SETTINGS_THEME 改变时无条件重建设置页,立即生效。
2026-05-15 02:26:54 +08:00
7015725
32a30babcc refactor: 精简主题模板为设置页主题二选一
去掉之前多余的6套模板配色(ocean/sunset/forest/mono等),
只保留一个清晰的二选一功能:

- 动物岛风 (默认) — 当前 Animal Island 固定配色,不变
- 系统莫奈色 — 设置页 UI 使用系统 Monet 色系,与主面板一致

改动:
- THEME_TEMPLATE → SETTINGS_THEME (animal/monet)
- 删除 th_04_theme.js 的 getThemeTemplate/applyThemeTemplate
- buildSettingsGroupPanelView 直接根据 SETTINGS_THEME 用
  ui.colors 的 Monet 色构造 T 结构
- setPendingValue 中 SETTINGS_THEME 切换时重建设置页
2026-05-15 02:24:28 +08:00
7015725
cb01591369 fix: 主题模板真正应用到设置页 UI
之前主题模板只影响了 getPanelBgColorInt/getPanelTextColorInt,
但设置页 UI(buildSettingsGroupPanelView)的颜色全部来自
getAnimalIslandTheme() 硬编码,完全不经过模板逻辑。

修复:
1. buildSettingsGroupPanelView 开头判断是否选了非 system 模板,
   若是则根据模板的 dayBg/dayText/nightBg/nightText 自动生成
   一套兼容 T 结构的配色,覆盖 Animal Island 的 T 对象
2. 模板值从 pendingUserCfg 读取(预览态)
3. setPendingValue 中当 key=THEME_TEMPLATE 时,通过
   replaceToolAppPage('settings_group') 重建整个设置页 UI,
   使模板切换立即生效
2026-05-15 02:11:38 +08:00
7015725
1c802f6948 fix: 主题模板真正应用到主面板渲染
根本问题: buildPanelView('main') 构建主面板时用的是
ui.colors.bgLight/bgDark(Monet 配色),完全不经过
getPanelBgColorInt/getPanelTextColorInt 的模板填充逻辑。
updatePanelBackground 虽然封装了统一取色,但实际没有在
主面板构建流程中被调用。

修复:
1. buildPanelView('main') 末尾调用 updatePanelBackground(panel)
   应用统一主题色(Monet/模板/自定义)
2. 保留临时背景色避免构建过程中的裸窗口闪烁
2026-05-15 02:08:58 +08:00
7015725
49d73786bf fix: 修复主题模板在设置页预览时不生效的问题
根因: applyThemeTemplate 读/写 this.config,但设置页预览
走的是 pendingUserCfg(临时编辑缓存),模板值在选择后只写入了
pendingUserCfg.THEME_TEMPLATE,applyThemeTemplate 读 this.config
则一直为 system/d 返回,导致模板不生效。

修复:
- applyThemeTemplate 接受 preview 参数,预览态从 pendingUserCfg
  读写,非预览态从 this.config 读写
- getPanelBgColorInt/getPanelTextColorInt 预览态优先从
  pendingUserCfg 读取那4个颜色配置
2026-05-15 02:00:25 +08:00
7015725
d2abbcca9f fix: schema 完整性检测添加 THEME_TEMPLATE 字段
旧设备上持久化的 schema.json 不含 THEME_TEMPLATE 项,
导致设置页的换装分组不显示主题模板选择器。
在 needReset 检测中补充该字段,触发旧 schema 自动重置。
2026-05-15 01:56:58 +08:00
7015725
d8a25aced9 feat: 添加主题模板一键切换功能
- 新增 THEME_TEMPLATE 配置项(system/animal/ocean/sunset/forest/mono)
- 新增 getThemeTemplate() 模板颜色映射
- 新增 applyThemeTemplate() 自动填充面板背景/文字色
- getPanelBgColorInt/getPanelTextColorInt 支持模板覆盖
- 设置页换装分组新增单选选择器
2026-05-15 01:54:40 +08:00
7015725
bba3d800af style: refine island settings home 2026-05-14 23:30:38 +08:00
7015725
7079b633bb style: soften balloon settings page 2026-05-14 23:24:48 +08:00
7015725
8907a2d5d8 style: soften animal island ToolApp copy 2026-05-14 23:17:09 +08:00
7015725
edb26dc593 feat: apply animal island style to ToolApp 2026-05-14 22:28:51 +08:00
7015725
b9409c9056 refactor: prioritize OnBackAnimationCallback 2026-05-14 18:44:57 +08:00
7015725
c92606246e fix: register ToolApp back animation after attach 2026-05-14 18:33:03 +08:00
7015725
47ca2f782f fix: stop fallback strips blocking system back 2026-05-14 18:28:56 +08:00
7015725
2b55e2bdd4 style: refresh ToolApp visual back experience 2026-05-14 18:23:31 +08:00
7015725
f1e2ab7c37 fix: register ToolApp animated back callback 2026-05-14 03:22:14 +08:00
7015725
a8cd7c193d fix: capture screen edge back for ToolApp 2026-05-14 03:10:25 +08:00
7015725
f8463490e1 fix: show ToolApp preview during system back gesture 2026-05-14 03:04:35 +08:00
7015725
65367f0d06 feat: add interactive ToolApp back preview 2026-05-14 03:00:15 +08:00
7015725
1d31638073 fix: add ToolApp edge swipe back fallback 2026-05-14 02:51:46 +08:00
7015725
96bbb77127 docs: update README for ToolApp settings 2026-05-13 18:45:33 +08:00
7015725
e394a0f078 Remove duplicate inline color palette 2026-05-13 18:28:06 +08:00
7015725
3d0e57e813 Match ShortX icon picker with settings UI 2026-05-13 18:21:55 +08:00
7015725
b9d3dc89da style: simplify button editor section descriptions 2026-05-13 18:10:07 +08:00
7015725
7fd1430207 fix: add bottom gap to button manager footer 2026-05-13 08:32:06 +08:00
7015725
a5ecbec23b fix: keep button manager footer visible 2026-05-13 08:27:51 +08:00
7015725
e9b6052c6a style: apply minimal button management layout 2026-05-13 08:21:12 +08:00
7015725
648b45585a style: simplify button manager layout 2026-05-13 08:15:45 +08:00
7015725
3b6830e0ec style: polish button manager and editor layout 2026-05-13 07:38:48 +08:00
7015725
09f2aa4d07 feat: adapt ToolApp size to screen 2026-05-13 07:31:28 +08:00
7015725
080cb9abf7 refactor: keep ToolApp as single root view 2026-05-13 07:27:39 +08:00
7015725
2a53c872eb fix: show predictive back affordance for settings overlay 2026-05-13 07:20:26 +08:00
7015725
efb14bcde0 feat: support predictive back gesture 2026-05-12 20:43:42 +08:00
7015725
c4b886f83d style: refine button icon appearance editor 2026-05-12 20:32:08 +08:00
7015725
53967f77d2 feat: add collapsible button editor sections 2026-05-12 20:17:11 +08:00
7015725
46393fe9ba Revert "feat: split button editor into app subpages"
This reverts commit 5a4e3f6423.
2026-05-12 20:10:23 +08:00
7015725
5a4e3f6423 feat: split button editor into app subpages 2026-05-12 20:01:45 +08:00
7015725
c1643924b7 feat: app-style button manager page 2026-05-12 19:11:29 +08:00
7015725
c41e1479b9 feat: app-style ToolHub settings home 2026-05-12 17:31:02 +08:00
7015725
ea72f6b2eb fix: keep ToolHub app stack state on editor return 2026-05-12 11:06:54 +08:00
7015725
a0b6b92f57 feat: introduce ToolHub settings page stack 2026-05-12 10:47:13 +08:00
18 changed files with 4112 additions and 607 deletions

View File

@@ -9,6 +9,12 @@ ShortX ToolHub 是一个面向 **ShortX / Rhino ES5 JS** 的模块化悬浮工
https://git.xin-blog.com/linshenjianlu/ShortX_ToolHub 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` 校验,通过后才覆盖本地文件。 - **SHA256 文件校验**:每个子模块按清单中的 `sha256``size` 校验,通过后才覆盖本地文件。
- **防回滚**:入口内置 `MIN_TRUSTED_MANIFEST_VERSION`,并记录本地已信任清单版本,拒绝旧版本清单。 - **防回滚**:入口内置 `MIN_TRUSTED_MANIFEST_VERSION`,并记录本地已信任清单版本,拒绝旧版本清单。
- **本地可信回退**:网络或远端清单异常时,不盲目覆盖;已验证过的本地模块可继续使用。 - **本地可信回退**:网络或远端清单异常时,不盲目覆盖;已验证过的本地模块可继续使用。
- **更新源切换**:入口支持在 Gitea 主源与 GitHub 镜像之间切换,下载流程仍统一走签名清单与 SHA256 校验。
- **App 化设置页**:设置主页、按钮管理、按钮编辑等页面使用统一 Shell 与页面栈,支持顶部返回/关闭。
- **系统返回适配**:支持导航键返回;悬浮窗内置左右边缘交互式滑动返回,拖动时实时露出上级页面,松手后完成返回。
- **ShortX 图标选择器**:支持图标点选、搜索、分页、自适应列数,不再依赖手填图标名。 - **ShortX 图标选择器**:支持图标点选、搜索、分页、自适应列数,不再依赖手填图标名。
- **颜色选择器**支持常用色、最近色、RGB、透明度和实时预览。 - **颜色选择器**使用折叠式完整调色板,支持常用色、最近色、RGB、透明度和实时预览;避免重复内联色板
- **自适应布局**ToolApp 根据屏幕尺寸调整宽高,按钮管理页底部操作区保持可见;悬浮球在横竖屏切换后会重新计算屏幕尺寸并保持吸边位置。
- **日志记录**:启动、更新、验签、加载异常写入 `ToolHub/logs/init.log` - **日志记录**:启动、更新、验签、加载异常写入 `ToolHub/logs/init.log`
--- ---
@@ -151,6 +161,13 @@ ShortX_ToolHub/
9. 校验通过才覆盖本地模块。 9. 校验通过才覆盖本地模块。
10. 所有模块加载正常后,保存本地可信清单版本。 10. 所有模块加载正常后,保存本地可信清单版本。
入口顶部可通过 `UPDATE_SOURCE` 选择更新源:
- `0`Gitea 主源
- `1`GitHub 镜像
无论选择哪个源,`manifest.json` / `manifest.sig` 验签、防回滚和子模块 SHA256 校验流程都保持一致。
当前 keyId 当前 keyId
```text ```text
@@ -176,13 +193,23 @@ toolhub-targets-2026-rsa3072
| `th_11_action.js` | 按钮动作分发与执行 | | `th_11_action.js` | 按钮动作分发与执行 |
| `th_12_rebuild.js` | 悬浮球重建逻辑 | | `th_12_rebuild.js` | 悬浮球重建逻辑 |
| `th_13_panel_ui.js` | 设置面板通用 UI 组件 | | `th_13_panel_ui.js` | 设置面板通用 UI 组件 |
| `th_14_panels.js` | 设置面、按钮编辑器、图标选择器、颜色选择器 | | `th_14_panels.js` | 设置子页面、按钮编辑器、图标选择器、颜色选择器 |
| `th_15_extra.js` | 主面板与附加展示层 | | `th_15_extra.js` | 主面板、ToolApp 页面栈、设置主页、按钮管理页 |
| `th_16_entry.js` | 生命周期、广播注册、启动与销毁 | | `th_16_entry.js` | 生命周期、广播注册、系统返回处理、启动与销毁 |
--- ---
## 图标颜色交互 ## 图标颜色与设置交互
### ToolApp 设置页
- 设置、按钮管理、按钮编辑统一运行在 ToolApp Shell 内。
- 顶部栏提供返回与关闭,子页面优先通过页面栈返回。
- 系统返回键会优先回到上一页;没有上一页时关闭 ToolApp。
- 悬浮窗无法稳定接入系统级预测性返回动画,因此 ToolHub 内置左右边缘交互式滑动返回作为替代。
- 拖动过程中当前页面跟随手指横移,上级页面在底层实时露出;松手达到阈值后完成返回,未达到阈值则回弹。
- 面板尺寸按屏幕自适应,减少小屏溢出与大屏空白。
- 按钮管理页底部操作区保持可见,避免被列表内容挤出屏幕。
### ShortX 图标选择器 ### 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 ### 2026-05-09
**文档更新** **文档更新**

View File

@@ -2,7 +2,16 @@
// 安全更新机制:入口内置 RSA 公钥,先验证 manifest.json/manifest.sig再按 SHA256 下载子模块。 // 安全更新机制:入口内置 RSA 公钥,先验证 manifest.json/manifest.sig再按 SHA256 下载子模块。
// Gitea 只负责分发;未通过签名/哈希/防回滚校验时,不覆盖本地模块。 // 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 GIT_BASE = GIT_ROOT + "code/";
var TRUSTED_PUBLIC_KEYS = { var TRUSTED_PUBLIC_KEYS = {
"toolhub-targets-2026-rsa3072": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEApiyhtMDJce7dVCxH1/oDu8kbiECYoT5XXmXvR/XNYuJ/5FuL83SbpCQ3QmUnqkbfNyOFqnxac/qlbXJtx6eeSotLP1HmrKI0LGymgxG6b1FfGHBfIKNZfBLIvzVDQob+HJfshlsS1JRlW5Jhm25TMh8dJCQQQZWW/ZItbtOvPYbLwG8cnqEdX8gqyB304+r2l35GPTfxZIGEK/9PcE3AMuqwTolMJsBHtG61hmMdz3dzTTEZQoOcciGWuwr2ZW8XkF6f5SgWkC29ZxZqAxceK4FJ8BsYirpFQxVKyZ6eiYlpNiYz+pHLP2U7JTO6ImmT1rlYSS6xw2tlWf0xq72nuOPC+VzEivuEhnC4y9WBSvauRa/ViIDgQ3yXl2MajuAvGSVWRfZ5Gz5Up8PQD7vxmHT2r0fA4xq4GIvUvGCqOG/d1FRrlVyEuNhCZ7KgpEKPno7fLnC6/ftnYcN5ZNOSWwjWH/e4fBxM5s6RRIYzIY2N0f/fqsRH42lWAhX5stujAgMBAAE=" "toolhub-targets-2026-rsa3072": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEApiyhtMDJce7dVCxH1/oDu8kbiECYoT5XXmXvR/XNYuJ/5FuL83SbpCQ3QmUnqkbfNyOFqnxac/qlbXJtx6eeSotLP1HmrKI0LGymgxG6b1FfGHBfIKNZfBLIvzVDQob+HJfshlsS1JRlW5Jhm25TMh8dJCQQQZWW/ZItbtOvPYbLwG8cnqEdX8gqyB304+r2l35GPTfxZIGEK/9PcE3AMuqwTolMJsBHtG61hmMdz3dzTTEZQoOcciGWuwr2ZW8XkF6f5SgWkC29ZxZqAxceK4FJ8BsYirpFQxVKyZ6eiYlpNiYz+pHLP2U7JTO6ImmT1rlYSS6xw2tlWf0xq72nuOPC+VzEivuEhnC4y9WBSvauRa/ViIDgQ3yXl2MajuAvGSVWRfZ5Gz5Up8PQD7vxmHT2r0fA4xq4GIvUvGCqOG/d1FRrlVyEuNhCZ7KgpEKPno7fLnC6/ftnYcN5ZNOSWwjWH/e4fBxM5s6RRIYzIY2N0f/fqsRH42lWAhX5stujAgMBAAE="

View File

@@ -1 +1 @@
55dbcacaaa31b031e9a0fcef1253c2e0403fca423ad969e0e1387815e69de3e7 ToolHub.js 458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js

View File

@@ -83,6 +83,7 @@ var ConfigValidator = {
PANEL_PADDING_DP: { type: "int", min: 8, max: 32, default: 12 }, 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_MODE: { type: "enum", values: [0, 1, 2], default: 1 },
THEME_ACCENT_LIGHT: { type: "string", default: "#FF3A86FF" }, THEME_ACCENT_LIGHT: { type: "string", default: "#FF3A86FF" },
THEME_ACCENT_DARK: { type: "string", default: "#FF90CAF9" }, 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_TYPE: { type: "enum", values: ["app", "file", "android", "shortx"], default: "app" },
BALL_ICON_RES_ID: { type: "int", min: 0, max: 999999, default: 0 }, 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_PNG_MODE: { type: "int", min: 0, max: 2, default: 1 },
BALL_IDLE_ALPHA: { type: "float", min: 0.1, max: 1.0, default: 0.6 }, 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_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 }, LONG_PRESS_VIBRATE_MS: { type: "int", min: 10, max: 100, default: 40 },
CLICK_SLOP_DP: { type: "int", min: 2, max: 20, default: 6 }, 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 }, ENABLE_SNAP_TO_EDGE: { type: "bool", default: true },
@@ -129,6 +136,7 @@ var ConfigValidator = {
BALL_ICON_PKG: { type: "string", default: "" }, BALL_ICON_PKG: { type: "string", default: "" },
BALL_ICON_RES_NAME: { type: "string", default: "" }, BALL_ICON_RES_NAME: { type: "string", default: "" },
BALL_ICON_TINT_HEX: { 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 }, BOUNCE_DECAY: { type: "float", min: 0.3, max: 0.95, default: 0.72 },
@@ -726,7 +734,13 @@ var ConfigManager = {
LONG_PRESS_VIBRATE_MS: 18, LONG_PRESS_VIBRATE_MS: 18,
ENABLE_LONG_PRESS: true, ENABLE_LONG_PRESS: true,
LONG_PRESS_MS: 520, LONG_PRESS_MS: 520,
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: 28,
CLICK_SLOP_DP: 6, 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, ENABLE_BOUNCE: true,
BOUNCE_TIMES: 2, BOUNCE_TIMES: 2,
BOUNCE_MAX_SCALE: 0.88, BOUNCE_MAX_SCALE: 0.88,
@@ -740,6 +754,7 @@ var ConfigManager = {
BALL_ICON_RES_NAME: "", BALL_ICON_RES_NAME: "",
BALL_ICON_SIZE_DP: 22, BALL_ICON_SIZE_DP: 22,
BALL_ICON_TINT_HEX: "", BALL_ICON_TINT_HEX: "",
BALL_BG_COLOR_HEX: "",
BALL_IDLE_ALPHA: 0.6, BALL_IDLE_ALPHA: 0.6,
PANEL_POS_GRAVITY: "bottom", PANEL_POS_GRAVITY: "bottom",
PANEL_CUSTOM_OFFSET_Y: 0, PANEL_CUSTOM_OFFSET_Y: 0,
@@ -754,6 +769,7 @@ var ConfigManager = {
PANEL_LABEL_TOP_MARGIN_DP: 4, PANEL_LABEL_TOP_MARGIN_DP: 4,
PANEL_BG_FALLBACK_HEX: "#EE1E1E1E", PANEL_BG_FALLBACK_HEX: "#EE1E1E1E",
PANEL_BG_ALPHA: 0.85, PANEL_BG_ALPHA: 0.85,
SETTINGS_THEME: "animal",
THEME_MODE: 1, THEME_MODE: 1,
THEME_DAY_BG_HEX: null, THEME_DAY_BG_HEX: null,
THEME_DAY_TEXT_HEX: null, THEME_DAY_TEXT_HEX: null,
@@ -770,6 +786,10 @@ var ConfigManager = {
], ],
defaultSchema: [ defaultSchema: [
{ type: "section", name: "外观" }, { 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_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_BG_HEX", name: "日间背景色(#RRGGBB)", type: "text" },
{ key: "THEME_DAY_TEXT_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 }, { key: "PANEL_BG_ALPHA", name: "面板背景透明度(0.1~1.0)", type: "float", min: 0.1, max: 1.0, step: 0.05 },
{ type: "section", name: "悬浮球" }, { type: "section", name: "悬浮球" },
{ key: "BALL_SIZE_DP", name: "悬浮球大小(dp)", type: "int", min: 28, max: 120, step: 1 }, { key: "BALL_SIZE_DP", name: "球大小", 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_PANEL_GAP_DP", name: "离小屋的距离", type: "int", min: 0, max: 60, step: 1 },
{ key: "BALL_ICON_TYPE", name: "图标类型", type: "single_choice", options: [ { key: "BALL_ICON_TYPE", name: "气球徽章", type: "single_choice", options: [
{ label: "应用图标 (app)", value: "app" }, { label: "应用头像", value: "app" },
{ label: "文件图标 (file)", value: "file" }, { label: "文件选择", value: "file" },
{ label: "ShortX内置 (shortx)", value: "shortx" } { label: "岛上图标库", value: "shortx" }
]}, ]},
{ key: "BALL_ICON_FILE_PATH", name: "图标路径(file模式)", type: "text" }, { key: "BALL_ICON_FILE_PATH", name: "已选择的图标文件", type: "text" },
{ key: "BALL_ICON_RES_NAME", name: "ShortX图标", type: "ball_shortx_icon" }, { key: "BALL_ICON_RES_NAME", name: "岛上图标", type: "ball_shortx_icon" },
{ key: "BALL_ICON_TINT_HEX", name: "图标颜色", type: "ball_color" }, { 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_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: "面板布局" }, { type: "section", name: "面板布局" },
{ key: "PANEL_ROWS", name: "面板可视行数(超出滚动)", type: "int", min: 1, max: 12, step: 1 }, { key: "PANEL_ROWS", name: "面板可视行数(超出滚动)", type: "int", min: 1, max: 12, step: 1 },
@@ -828,8 +850,14 @@ var ConfigManager = {
{ type: "section", name: "触摸与手势" }, { type: "section", name: "触摸与手势" },
{ key: "CLICK_SLOP_DP", name: "点击位移阈值(dp)", type: "int", min: 1, max: 40, step: 1 }, { 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: "ENABLE_LONG_PRESS", name: "启用长按", type: "bool" },
{ key: "LONG_PRESS_MS", name: "长按判定(ms)", type: "int", min: 200, max: 2000, step: 10 }, { 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_HAPTIC_ENABLE", name: "长按震动反馈", type: "bool" },
{ key: "LONG_PRESS_VIBRATE_MS", name: "震动时长(ms)", type: "int", min: 1, max: 120, step: 1 }, { key: "LONG_PRESS_VIBRATE_MS", name: "震动时长(ms)", type: "int", min: 1, max: 120, step: 1 },
@@ -864,9 +892,46 @@ var ConfigManager = {
var needReset = false; var needReset = false;
if (s) { if (s) {
var sStr = JSON.stringify(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; 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 { } else {
// # 仅当文件不存在时才标记为需要重置(新建),避免因读取失败导致覆盖 // # 仅当文件不存在时才标记为需要重置(新建),避免因读取失败导致覆盖
try { try {

View File

@@ -42,6 +42,26 @@ function FloatBallAppWM(logger) {
viewerPanel: null, viewerPanel: null,
viewerPanelLp: null, viewerPanelLp: null,
viewerPanelType: 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, mask: null,
maskLp: null, maskLp: null,
@@ -71,6 +91,9 @@ function FloatBallAppWM(logger) {
pendingUserCfg: null, pendingUserCfg: null,
pendingDirty: false, pendingDirty: false,
// 按钮管理首页:搜索过滤状态
buttonManagerQuery: "",
closing: false closing: false
}; };

View File

@@ -2,13 +2,101 @@
// =======================【工具:屏幕/旋转】====================== // =======================【工具:屏幕/旋转】======================
FloatBallAppWM.prototype.getScreenSizePx = function() { FloatBallAppWM.prototype.getScreenSizePx = function() {
var m = new android.util.DisplayMetrics(); var m = new android.util.DisplayMetrics();
try { this.state.wm.getDefaultDisplay().getRealMetrics(m); } catch (e) { this.state.wm.getDefaultDisplay().getMetrics(m); } var w = 0;
return { w: m.widthPixels, h: m.heightPixels }; 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; }; FloatBallAppWM.prototype.getRotation = function() { try { return this.state.wm.getDefaultDisplay().getRotation(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } return -1; };
// =======================【工具alpha/toast/vibrate】====================== // =======================【工具alpha/toast/vibrate】======================
FloatBallAppWM.prototype.withAlpha = function(colorInt, alpha01) { var a = Math.floor(Number(alpha01) * 255); return (colorInt & 0x00FFFFFF) | (a << 24); }; FloatBallAppWM.prototype.withAlpha = function(colorInt, alpha01) { var a = Math.floor(Number(alpha01) * 255); return (colorInt & 0x00FFFFFF) | (a << 24); };
// Animal Island LiteToolApp 专用视觉主题。只供设置/按钮管理/编辑页调用,避免覆盖悬浮球 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.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) { FloatBallAppWM.prototype.vibrateOnce = function(ms) {
if (!this.config.LONG_PRESS_HAPTIC_ENABLE) return; if (!this.config.LONG_PRESS_HAPTIC_ENABLE) return;
@@ -58,7 +146,12 @@ FloatBallAppWM.prototype.ui = {
// Monet 扩展字段(供面板直接使用) // Monet 扩展字段(供面板直接使用)
_monetSurface: android.graphics.Color.parseColor("#F8F9FA"), _monetSurface: android.graphics.Color.parseColor("#F8F9FA"),
_monetOnSurface: android.graphics.Color.parseColor("#1F1F1F"), _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"), _monetOutline: android.graphics.Color.parseColor("#747775"),
_monetOutlineVariant: android.graphics.Color.parseColor("#C4C7C5"),
_monetOnPrimary: android.graphics.Color.parseColor("#FFFFFF"), _monetOnPrimary: android.graphics.Color.parseColor("#FFFFFF"),
_monetPrimaryContainer: android.graphics.Color.parseColor("#D3E3FD"), _monetPrimaryContainer: android.graphics.Color.parseColor("#D3E3FD"),
_monetOnPrimaryContainer: android.graphics.Color.parseColor("#041E49"), _monetOnPrimaryContainer: android.graphics.Color.parseColor("#041E49"),
@@ -117,7 +210,12 @@ FloatBallAppWM.prototype.ui = {
var rippleColor = app.withAlpha ? app.withAlpha(txtColor, 0.1) : 0x22888888; var rippleColor = app.withAlpha ? app.withAlpha(txtColor, 0.1) : 0x22888888;
btn.setBackground(this.createTransparentRippleDrawable(rippleColor, app.dp(8))); btn.setBackground(this.createTransparentRippleDrawable(rippleColor, app.dp(8)));
btn.setOnClickListener(new android.view.View.OnClickListener({ 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; return btn;
}, },
@@ -135,7 +233,12 @@ FloatBallAppWM.prototype.ui = {
btn.setBackground(this.createRippleDrawable(bgColor, pressedColor, app.dp(24))); btn.setBackground(this.createRippleDrawable(bgColor, pressedColor, app.dp(24)));
try { btn.setElevation(app.dp(2)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } try { btn.setElevation(app.dp(2)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
btn.setOnClickListener(new android.view.View.OnClickListener({ 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; return btn;
}, },
@@ -188,21 +291,30 @@ FloatBallAppWM.prototype.ui = {
var pasteBtn = this.createFlatButton(app, "粘贴", this.colors.accent, function() { var pasteBtn = this.createFlatButton(app, "粘贴", this.colors.accent, function() {
try { try {
var cb = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE); var cb = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE);
if (cb.hasPrimaryClip()) { if (!cb || !cb.hasPrimaryClip()) {
var item = cb.getPrimaryClip().getItemAt(0);
if (item) {
var txt = item.getText();
if (txt) {
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("剪贴板为空"); 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 === 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());
} catch (eP) { } catch (eP) {
app.toast("粘贴失败: " + eP); app.toast("粘贴失败: " + eP);
} }
@@ -345,6 +457,9 @@ var MonetColorProvider = {
surface: android.graphics.Color.parseColor("#F8F9FA"), surface: android.graphics.Color.parseColor("#F8F9FA"),
onSurface: android.graphics.Color.parseColor("#1F1F1F"), onSurface: android.graphics.Color.parseColor("#1F1F1F"),
surfaceVariant: android.graphics.Color.parseColor("#E1E3E1"), 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"), onSurfaceVariant: android.graphics.Color.parseColor("#5F6368"),
outline: android.graphics.Color.parseColor("#747775"), outline: android.graphics.Color.parseColor("#747775"),
outlineVariant: android.graphics.Color.parseColor("#C4C7C5"), outlineVariant: android.graphics.Color.parseColor("#C4C7C5"),
@@ -366,6 +481,9 @@ var MonetColorProvider = {
surface: android.graphics.Color.parseColor("#131314"), surface: android.graphics.Color.parseColor("#131314"),
onSurface: android.graphics.Color.parseColor("#E3E3E3"), onSurface: android.graphics.Color.parseColor("#E3E3E3"),
surfaceVariant: android.graphics.Color.parseColor("#49454F"), 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"), onSurfaceVariant: android.graphics.Color.parseColor("#C4C7C5"),
outline: android.graphics.Color.parseColor("#8E918F"), outline: android.graphics.Color.parseColor("#8E918F"),
outlineVariant: android.graphics.Color.parseColor("#49454F"), outlineVariant: android.graphics.Color.parseColor("#49454F"),
@@ -390,6 +508,9 @@ var MonetColorProvider = {
surface: "system_neutral1_900", surface: "system_neutral1_900",
onSurface: "system_neutral1_100", onSurface: "system_neutral1_100",
surfaceVariant: "system_neutral2_700", surfaceVariant: "system_neutral2_700",
surfaceContainerLow: "system_neutral1_800",
surfaceContainer: "system_neutral1_800",
surfaceContainerHigh: "system_neutral1_700",
onSurfaceVariant: "system_neutral2_200", onSurfaceVariant: "system_neutral2_200",
outline: "system_neutral2_400", outline: "system_neutral2_400",
outlineVariant: "system_neutral2_700", outlineVariant: "system_neutral2_700",
@@ -407,6 +528,9 @@ var MonetColorProvider = {
surface: "system_neutral1_10", surface: "system_neutral1_10",
onSurface: "system_neutral1_900", onSurface: "system_neutral1_900",
surfaceVariant: "system_neutral2_100", surfaceVariant: "system_neutral2_100",
surfaceContainerLow: "system_neutral1_50",
surfaceContainer: "system_neutral1_100",
surfaceContainerHigh: "system_neutral1_200",
onSurfaceVariant: "system_neutral2_700", onSurfaceVariant: "system_neutral2_700",
outline: "system_neutral2_500", outline: "system_neutral2_500",
outlineVariant: "system_neutral2_200", outlineVariant: "system_neutral2_200",
@@ -558,19 +682,19 @@ FloatBallAppWM.prototype.refreshMonetColors = function(forceDark) {
// 浅色配色 // 浅色配色
c.bgLight = ml.surface; c.bgLight = ml.surface;
c.cardLight = ml.surfaceVariant; c.cardLight = ml.surfaceContainerLow || ml.surfaceVariant;
c.textPriLight = ml.onSurface; c.textPriLight = ml.onSurface;
c.textSecLight = ml.onSurfaceVariant; c.textSecLight = ml.onSurfaceVariant;
c.dividerLight = ml.outline; c.dividerLight = ml.outlineVariant || ml.outline;
c.inputBgLight = ml.surface; c.inputBgLight = ml.surfaceContainerHigh || ml.surface;
// 深色配色 // 深色配色
c.bgDark = md.surface; c.bgDark = md.surface;
c.cardDark = md.surfaceVariant; c.cardDark = md.surfaceContainerLow || md.surfaceVariant;
c.textPriDark = md.onSurface; c.textPriDark = md.onSurface;
c.textSecDark = md.onSurfaceVariant; c.textSecDark = md.onSurfaceVariant;
c.dividerDark = md.outline; c.dividerDark = md.outlineVariant || md.outline;
c.inputBgDark = md.surface; c.inputBgDark = md.surfaceContainerHigh || md.surface;
// 当前主题配色(随主题切换) // 当前主题配色(随主题切换)
c.primary = m.primary; c.primary = m.primary;
@@ -585,7 +709,12 @@ FloatBallAppWM.prototype.refreshMonetColors = function(forceDark) {
// 扩展:完整 Monet 语义字段(供面板方法直接使用) // 扩展:完整 Monet 语义字段(供面板方法直接使用)
c._monetSurface = m.surface; c._monetSurface = m.surface;
c._monetOnSurface = m.onSurface; 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._monetOutline = m.outline;
c._monetOutlineVariant = m.outlineVariant || m.outline;
c._monetOnPrimary = m.onPrimary; c._monetOnPrimary = m.onPrimary;
c._monetPrimaryContainer = m.primaryContainer; c._monetPrimaryContainer = m.primaryContainer;
c._monetOnPrimaryContainer = m.onPrimaryContainer; c._monetOnPrimaryContainer = m.onPrimaryContainer;
@@ -642,6 +771,14 @@ FloatBallAppWM.prototype.getMonetAccentForBall = function() {
FloatBallAppWM.prototype.updateBallContentBackground = function(contentView) { FloatBallAppWM.prototype.updateBallContentBackground = function(contentView) {
try { try {
var ballColor = this.getMonetAccentForBall(); 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 dark = this.isDarkTheme();
var alpha01 = dark ? this.config.BALL_RIPPLE_ALPHA_DARK : this.config.BALL_RIPPLE_ALPHA_LIGHT; var alpha01 = dark ? this.config.BALL_RIPPLE_ALPHA_DARK : this.config.BALL_RIPPLE_ALPHA_LIGHT;
var rippleColor = this.withAlpha(ballColor, alpha01); var rippleColor = this.withAlpha(ballColor, alpha01);
@@ -761,33 +898,49 @@ FloatBallAppWM.prototype.applyTextColorRecursive = function(v, colorInt) {
}; };
FloatBallAppWM.prototype.updatePanelBackground = function(panelView) { FloatBallAppWM.prototype.updatePanelBackground = function(panelView) {
// 这段代码的主要内容/用途:统一为"主面板/设置面板/查看器面板"应用背景与文字颜色(自动/亮/暗三档),并输出调试日志(命中哪个颜色) // 这段代码的主要内容/用途:统一为"主面板/设置面板/查看器面板"应用背景与文字颜色SETTINGS_THEME=animal 时使用动物岛色monet 时保持系统莫奈
try { try {
var bg = new android.graphics.drawable.GradientDrawable(); 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); bg.setColor(bgInt);
// 轻量描边:亮色时更明显,暗色时也保留一点边界(不提供自定义输入,避免设置页复杂化) // 轻量描边:亮色时更明显,暗色时也保留一点边界(不提供自定义输入,避免设置页复杂化)
var sw = this.dp(1); 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)); } try { bg.setStroke(sw, stroke); } catch(eS) { safeLog(null, 'e', "catch " + String(eS)); }
panelView.setBackground(bg); panelView.setBackground(bg);
var tc = this.getPanelTextColorInt(bgInt);
try { themeBgInt = bgInt; themeTextInt = tc; } catch(eT) { safeLog(null, 'e', "catch " + String(eT)); } try { themeBgInt = bgInt; themeTextInt = tc; } catch(eT) { safeLog(null, 'e', "catch " + String(eT)); }
this.applyTextColorRecursive(panelView, tc); 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 { try {
_th_log(this.L, "d", _th_log(this.L, "d",
"[theme:apply] isDark=" + isDark + "[theme:apply] theme=" + settTheme +
" isDark=" + isDark +
" bg=" + _th_hex(bgInt) + " " + _th_argb(bgInt) + " bg=" + _th_hex(bgInt) + " " + _th_argb(bgInt) +
" text=" + _th_hex(tc) + " " + _th_argb(tc) + " text=" + _th_hex(tc) + " " + _th_argb(tc) +
" stroke=" + _th_hex(stroke) " stroke=" + _th_hex(stroke)

View File

@@ -153,7 +153,23 @@ FloatBallAppWM.prototype.setPendingValue = function(k, v) {
if (!this.state.pendingUserCfg) this.beginEditConfig(); if (!this.state.pendingUserCfg) this.beginEditConfig();
this.state.pendingUserCfg[k] = v; this.state.pendingUserCfg[k] = v;
this.state.pendingDirty = true; 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); this.refreshPreview(k);
} }
}; };
@@ -301,7 +317,32 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
} catch(eLK) { safeLog(null, 'e', "catch " + String(eLK)); } } catch(eLK) { safeLog(null, 'e', "catch " + String(eLK)); }
return; 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" || if (k === "PANEL_ROWS" || k === "PANEL_COLS" ||
k === "PANEL_ITEM_SIZE_DP" || k === "PANEL_GAP_DP" || k === "PANEL_ITEM_SIZE_DP" || k === "PANEL_GAP_DP" ||

View File

@@ -1577,12 +1577,13 @@ FloatBallAppWM.prototype.showIconPicker = function(opts) {
if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) { if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) {
try { try {
var rect = new android.graphics.Rect(); var rect = new android.graphics.Rect();
if (state.root) { if (panel) {
state.root.getGlobalVisibleRect(rect); panel.getGlobalVisibleRect(rect);
var x = e.getRawX(); var x = e.getRawX();
var y = e.getRawY(); var y = e.getRawY();
if (!rect.contains(x, y)) { if (!rect.contains(x, y)) {
hide(); hide();
return true;
} }
} }
} catch(eOut) { safeLog(null, 'e', "catch " + String(eOut)); } } catch(eOut) { safeLog(null, 'e', "catch " + String(eOut)); }

View File

@@ -127,6 +127,8 @@ FloatBallAppWM.prototype.playBounce = function(v) {
FloatBallAppWM.prototype.safeRemoveView = function(v, whichName) { FloatBallAppWM.prototype.safeRemoveView = function(v, whichName) {
try { try {
if (!v) return { ok: true, skipped: true }; 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); this.state.wm.removeView(v);
return { ok: true }; return { ok: true };
} catch (e) { } catch (e) {
@@ -182,10 +184,22 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
if (!this.state.addedViewer) return; if (!this.state.addedViewer) return;
if (!this.state.viewerPanel) return; if (!this.state.viewerPanel) return;
var oldViewerType = String(this.state.viewerPanelType || "");
this.safeRemoveView(this.state.viewerPanel, "viewerPanel"); this.safeRemoveView(this.state.viewerPanel, "viewerPanel");
this.state.viewerPanel = null; this.state.viewerPanel = null;
this.state.viewerPanelLp = null; this.state.viewerPanelLp = null;
this.state.viewerPanelType = 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.state.addedViewer = false;
this.hideMask(); this.hideMask();
@@ -205,6 +219,9 @@ FloatBallAppWM.prototype.handlePanelBack = function(which, reason) {
if (this.state.addedViewer) { if (this.state.addedViewer) {
var vt = String(this.state.viewerPanelType || w || "viewer"); 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 (vt === "btn_editor") {
if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) { if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) {
this.state.editingButtonIndex = null; this.state.editingButtonIndex = null;
@@ -262,6 +279,254 @@ FloatBallAppWM.prototype.handleSystemUiDismiss = function(reason) {
return false; 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 pipelineoverlay 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) { FloatBallAppWM.prototype.attachPanelSystemKeyHandler = function(panel, which) {
try { try {
if (!panel) return; if (!panel) return;
@@ -279,7 +544,25 @@ FloatBallAppWM.prototype.attachPanelSystemKeyHandler = function(panel, which) {
return false; 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) { } catch (e) {
safeLog(this.L, 'e', "attachPanelSystemKeyHandler fail which=" + String(which || "") + " err=" + String(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() { FloatBallAppWM.prototype.hideAllPanels = function() {
try { if (this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
this.hideMainPanel(); this.hideMainPanel();
this.hideSettingsPanel(); this.hideSettingsPanel();
this.hideViewerPanel(); this.hideViewerPanel();
this.state.toolAppActive = false;
this.state.toolAppRoute = null;
this.state.toolAppNavStack = [];
this.state.settingsGroupKey = null;
this.hideMask(); this.hideMask();
this._clearHeavyCachesIfAllHidden("hideAllPanels"); this._clearHeavyCachesIfAllHidden("hideAllPanels");
@@ -397,6 +685,13 @@ FloatBallAppWM.prototype.snapToEdgeDocked = function(withAnim, forceSide) {
// 如果需要保护,调用方自己判断 // 如果需要保护,调用方自己判断
if (this.state.dragging) return; 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 di = this.getDockInfo();
var ballSize = di.ballSize; var ballSize = di.ballSize;
var visible = di.visiblePx; 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.closing) return;
if (!this.state.addedBall) return; if (!this.state.addedBall) return;
var di = this.getDockInfo();
var oldW = this.state.screen.w; var oldW = this.state.screen.w;
var oldH = this.state.screen.h; var oldH = this.state.screen.h;
@@ -643,11 +936,23 @@ FloatBallAppWM.prototype.onScreenChangedReflow = function() {
var newH = newScreen.h; var newH = newScreen.h;
if (newW <= 0 || newH <= 0) return; 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 (oldW <= 0) oldW = newW;
if (oldH <= 0) oldH = newH; if (oldH <= 0) oldH = newH;
this.state.screen = { w: newW, h: newH }; this.state.screen = { w: newW, h: newH };
var di = this.getDockInfo();
var ballSize = di.ballSize; var ballSize = di.ballSize;
var visible = di.visiblePx; var visible = di.visiblePx;
var hidden = di.hiddenPx; 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)); } 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); 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() { FloatBallAppWM.prototype.setupDisplayMonitor = function() {
@@ -730,7 +1073,7 @@ FloatBallAppWM.prototype.setupDisplayMonitor = function() {
if (changed) { if (changed) {
self.cancelDockTimer(); self.cancelDockTimer();
self.onScreenChangedReflow(); self.scheduleScreenReflow("display_changed");
self.touchActivity(); self.touchActivity();
} }
} catch (e1) { } catch (e1) {

View File

@@ -3,7 +3,9 @@
FloatBallAppWM.prototype.createSectionHeader = function(item, parent) { FloatBallAppWM.prototype.createSectionHeader = function(item, parent) {
var isDark = this.isDarkTheme(); var isDark = this.isDarkTheme();
var C = this.ui.colors; 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); var h = new android.widget.TextView(context);
h.setText(String(item.name || "")); h.setText(String(item.name || ""));
@@ -17,11 +19,14 @@ FloatBallAppWM.prototype.createSectionHeader = function(item, parent) {
FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivider) { FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivider) {
var isDark = this.isDarkTheme(); var isDark = this.isDarkTheme();
var C = this.ui.colors; var C = this.ui.colors;
var textColor = isDark ? C.textPriDark : C.textPriLight; var T = this.getAnimalIslandTheme ? this.getAnimalIslandTheme() : null;
var secColor = isDark ? C.textSecDark : C.textSecLight; try { if (this.applySettingsTheme && T) this.applySettingsTheme(T, isDark, C, this.state.pendingUserCfg || this.config); } catch(eTheme) { safeLog(null, 'e', "catch " + String(eTheme)); }
var dividerColor = isDark ? C.dividerDark : C.dividerLight; var textColor = T ? T.text : (isDark ? C.textPriDark : C.textPriLight);
var primary = C.primary; var secColor = T ? T.sub : (isDark ? C.textSecDark : C.textSecLight);
var switchOff = isDark ? (0xFF555555 | 0) : (0xFFCCCCCC | 0); 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); 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.setBackground(self.ui.createTransparentRippleDrawable(primary, self.dp(16)));
function runSettingAction() {
try {
self.touchActivity();
var action = String(item.action || "");
if (action === "open_btn_mgr") {
self.showPanelAvoidBall("btn_editor");
return;
}
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({ btn.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) { onClick: function(v) {
try { runSettingAction();
self.touchActivity();
if (item.action === "open_btn_mgr") {
self.showPanelAvoidBall("btn_editor");
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
} }
})); }));
row.addView(btn); row.addView(btn);
@@ -247,12 +284,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
// 行点击也触发 // 行点击也触发
row.setOnClickListener(new android.view.View.OnClickListener({ row.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) { onClick: function(v) {
try { runSettingAction();
self.touchActivity();
if (item.action === "open_btn_mgr") {
self.showPanelAvoidBall("btn_editor");
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
} }
})); }));
@@ -271,7 +303,8 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
et.setText(String(curVal)); et.setText(String(curVal));
et.setTextColor(textColor); et.setTextColor(textColor);
et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); 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.setPadding(self.dp(8), self.dp(8), self.dp(8), self.dp(8));
et.setSingleLine(true); et.setSingleLine(true);
@@ -407,7 +440,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
} }
refreshBallShortXPreview(); refreshBallShortXPreview();
var btnPick = self.ui.createFlatButton(self, "选择图标", primary, function() { var btnPick = self.ui.createFlatButton(self, "换一个", primary, function() {
self.touchActivity(); self.touchActivity();
self.showShortXIconPickerPopup({ self.showShortXIconPickerPopup({
currentName: String(self.getPendingValue(item.key) || ""), 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)); gapView.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
iconRow.addView(gapView); iconRow.addView(gapView);
var btnClear = self.ui.createFlatButton(self, "清空", secColor, function() { var btnClear = self.ui.createFlatButton(self, "不用图标", secColor, function() {
self.touchActivity(); self.touchActivity();
try { try {
self.setPendingValue(item.key, ""); self.setPendingValue(item.key, "");
@@ -484,7 +517,7 @@ FloatBallAppWM.prototype.createSettingItemView = function(item, parent, needDivi
} }
refreshBallColorPreview(); refreshBallColorPreview();
var btnColor = self.ui.createFlatButton(self, "选择颜色", primary, function() { var btnColor = self.ui.createFlatButton(self, "颜色", primary, function() {
self.touchActivity(); self.touchActivity();
self.showColorPickerPopup({ self.showColorPickerPopup({
currentColor: String(self.getPendingValue(item.key) || ""), 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)); gapColorView.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
colorRow.addView(gapColorView); colorRow.addView(gapColorView);
var btnClearColor = self.ui.createFlatButton(self, "清空", secColor, function() { var btnClearColor = self.ui.createFlatButton(self, "恢复默认", secColor, function() {
self.touchActivity(); self.touchActivity();
try { try {
self.setPendingValue(item.key, ""); self.setPendingValue(item.key, "");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -229,7 +229,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
if (act === android.content.Intent.ACTION_CONFIGURATION_CHANGED) { if (act === android.content.Intent.ACTION_CONFIGURATION_CHANGED) {
self.cancelDockTimer(); self.cancelDockTimer();
self.onScreenChangedReflow(); self.scheduleScreenReflow("configuration_changed");
self.touchActivity(); self.touchActivity();
} }

View File

@@ -2,40 +2,40 @@
"alg": "SHA256withRSA", "alg": "SHA256withRSA",
"files": { "files": {
"th_01_base.js": { "th_01_base.js": {
"sha256": "30c121902ef5a0006a5daabca8443ed6cbf6e2508209604c48f89855d0690034", "sha256": "f228e3fda85ab9946934a9950515556d75df110da88432b339258036b8f3a3e2",
"size": 52546 "size": 57166
}, },
"th_02_core.js": { "th_02_core.js": {
"sha256": "f44f88f0ce3f44f0d1675e55a9f0b66d831caa7819dda54aef6e308ec27faaeb", "sha256": "3c5c498d200e961d48fc9ca3f885475e770ecb32b83ec6a89d23df3f88aed1c9",
"size": 3934 "size": 4664
}, },
"th_03_icon.js": { "th_03_icon.js": {
"sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987", "sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987",
"size": 5598 "size": 5598
}, },
"th_04_theme.js": { "th_04_theme.js": {
"sha256": "b815acfee30e56458e61c033ab3fddfe5d8d0c52ec172c419bad7187fcb341ba", "sha256": "16a7121c734fccae17b9f8a23c1f0758d3a765541d1de4d6159747fc92bc26a4",
"size": 36083 "size": 42568
}, },
"th_05_persistence.js": { "th_05_persistence.js": {
"sha256": "d80787c2810839ebbe499e93db3df33d6e8d2d6b6ae71644ce351db0f36e4d3e", "sha256": "1bec9f5bda8e5ae82f2a42707c59e72f4819fffc200d74ae55f191a76009af9b",
"size": 14077 "size": 15754
}, },
"th_06_icon_parser.js": { "th_06_icon_parser.js": {
"sha256": "25b95a5df634a7ee359f3ab798e4d3154a71c24016f7b4bf8a658096644b2484", "sha256": "25b95a5df634a7ee359f3ab798e4d3154a71c24016f7b4bf8a658096644b2484",
"size": 22909 "size": 22909
}, },
"th_07_shortcut.js": { "th_07_shortcut.js": {
"sha256": "7b2dbd1e35c636cca4ccce335dfb9e0b972342972ce012116ff4bbcfc438caa1", "sha256": "8e70adda0d73930e2afc1eba488e95016fc2f739ba199834a30bec015fa6156c",
"size": 72992 "size": 73011
}, },
"th_08_content.js": { "th_08_content.js": {
"sha256": "8a76f15dfd1292081cba4b2dd218424be66540350e2807065421a6176a86c2db", "sha256": "8a76f15dfd1292081cba4b2dd218424be66540350e2807065421a6176a86c2db",
"size": 7938 "size": 7938
}, },
"th_09_animation.js": { "th_09_animation.js": {
"sha256": "7120d208910955a2a163c4ad535b2eca674f7a0c2c462ef2f03bad11c9511933", "sha256": "2570ffbbc33fa2318e27c5e99c4e78e1bf96c7da8ec134053acda009207488cb",
"size": 27541 "size": 42007
}, },
"th_10_shell.js": { "th_10_shell.js": {
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc", "sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
@@ -50,23 +50,23 @@
"size": 2362 "size": 2362
}, },
"th_13_panel_ui.js": { "th_13_panel_ui.js": {
"sha256": "19e5e1c346051aafdda6253ed7c25ef5fbc4f883de66f656c9575d97e9d6ffc8", "sha256": "9052e01ccdc0af29615ef8e72550d7d312240f9ab36ebfb66e673c92ab2e4b9c",
"size": 20386 "size": 22308
}, },
"th_14_panels.js": { "th_14_panels.js": {
"sha256": "cf18cb06a9e221b671360f94040542c1c27e8776bb5037abe4f4b0f3de3e1073", "sha256": "4b73af93ff5b8fa8ffadf76f936732b93a32ccff8a6623b96f9ad3bbb8114ec8",
"size": 217347 "size": 304993
}, },
"th_15_extra.js": { "th_15_extra.js": {
"sha256": "ed56b19a5a798a785c024eb931140ded69d16921d2e87ad4ccd861df1c1907d8", "sha256": "fb8349a61771903a8c2a3ca6fee15f8df88af79b684e7b444b35c38bcf354ed5",
"size": 62936 "size": 117741
}, },
"th_16_entry.js": { "th_16_entry.js": {
"sha256": "e7c99c3dfbd6aedab05551426955081ae6cae034754f2f557cefa01dc75dc001", "sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1",
"size": 12777 "size": 12799
} }
}, },
"keyId": "toolhub-targets-2026-rsa3072", "keyId": "toolhub-targets-2026-rsa3072",
"schema": 2, "schema": 2,
"version": 20260512022403 "version": 20260521235402
} }

View File

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

View 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")

View 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")

View 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")