Revert "refactor: split picker module and improve diagnostics"

This reverts commit b36af7f78a.
This commit is contained in:
7015725
2026-05-22 00:09:59 +08:00
parent b36af7f78a
commit a6b0e1b41b
11 changed files with 1937 additions and 1187 deletions

View File

@@ -42,9 +42,7 @@ function writeLog(msg) {
var writer = new java.io.FileWriter(f, true);
writer.write("[" + ts + "] " + String(msg) + "\n");
writer.close();
} catch (e) {
try { android.util.Log.e("ToolHub", "writeLog failed: " + String(e)); } catch(eLog) {}
}
} catch (e) {}
}
function runShell(cmdArr) {
@@ -52,7 +50,7 @@ function runShell(cmdArr) {
var proc = java.lang.Runtime.getRuntime().exec(cmdArr);
proc.waitFor();
return proc.exitValue() === 0;
} catch (e) { writeLog("runShell failed: " + String(e)); return false; }
} catch (e) { return false; }
}
function checkDirPerms(path) {
@@ -66,7 +64,7 @@ function checkDirPerms(path) {
var parts = String(line).trim().split(/\s+/);
if (parts.length >= 3) return String(parts[0]) === "1000" && String(parts[1]) === "1000" && String(parts[2]) === "700";
}
} catch (e) { writeLog("checkDirPerms failed: " + String(e)); }
} catch (e) {}
return false;
}
@@ -102,7 +100,7 @@ function readTextFile(path) {
while ((line = r.readLine()) != null) sb.append(line).append("\n");
r.close();
return String(sb.toString());
} catch (e) { writeLog("readTextFile failed: " + String(path) + " err=" + String(e)); return null; }
} catch (e) { return null; }
}
function writeTextFile(path, text) {
@@ -114,7 +112,7 @@ function writeTextFile(path, text) {
w.write(String(text));
w.close();
return true;
} catch (e) { writeLog("writeTextFile failed: " + String(path) + " err=" + String(e)); return false; }
} catch (e) { return false; }
}
function readFirstLine(path) {
@@ -286,15 +284,15 @@ function ensureVerifiedModule(relPath, destFile) {
}
var tmpFile = new java.io.File(destFile.getAbsolutePath() + ".tmp");
try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) { writeLog("tmp delete before download failed: " + String(eDelTmp0)); }
try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) {}
var size = downloadFile(GIT_BASE + relPath, tmpFile);
if (expectedSize > 0 && size !== expectedSize) {
try { tmpFile.delete(); } catch (eDelSize) { writeLog("tmp delete after size mismatch failed: " + String(eDelSize)); }
try { tmpFile.delete(); } catch (eDelSize) {}
throw "manifest size mismatch for " + relPath + ": expected=" + expectedSize + ", got=" + size;
}
var tmpHash = sha256File(tmpFile.getAbsolutePath());
if (!tmpHash || String(tmpHash).toLowerCase() !== expectedHash) {
try { tmpFile.delete(); } catch (eDelHash) { writeLog("tmp delete after hash mismatch failed: " + String(eDelHash)); }
try { tmpFile.delete(); } catch (eDelHash) {}
throw "manifest SHA256 mismatch for " + relPath + ": expected=" + expectedHash + ", actual=" + tmpHash;
}
var wasNew = !destFile.exists();
@@ -335,7 +333,7 @@ function loadScript(relPath) {
geval(String(code));
} catch(e) {
var errMsg = "loadScript(" + relPath + ") failed: " + e;
try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) { writeLog("android log failed: " + String(eLog)); }
try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) {}
throw errMsg;
}
}
@@ -343,7 +341,7 @@ function loadScript(relPath) {
var modules = ["th_01_base.js", "th_02_core.js", "th_03_icon.js", "th_04_theme.js", "th_05_persistence.js",
"th_06_icon_parser.js", "th_07_shortcut.js", "th_08_content.js", "th_09_animation.js",
"th_10_shell.js", "th_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js",
"th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js"];
"th_14_panels.js", "th_15_extra.js", "th_16_entry.js"];
var __moduleUpdates = [];
var loadErrors = [];
var criticalModules = { "th_01_base.js": true, "th_16_entry.js": true };
@@ -355,7 +353,7 @@ for (var i = 0; i < modules.length; i++) {
} catch (e) {
var modErr = "Module load failed: " + modules[i] + " -> " + String(e);
writeLog(modErr);
try { android.util.Log.e("ToolHub", modErr); } catch(eLog) { writeLog("android log failed: " + String(eLog)); }
try { android.util.Log.e("ToolHub", modErr); } catch(eLog) {}
loadErrors.push({ module: modules[i], err: String(e) });
if (criticalModules[modules[i]]) throw "Critical module failed: " + modules[i] + " (" + String(e) + ")";
}
@@ -406,7 +404,7 @@ var __out = (function() {
var startRet = null;
try { startRet = app.startAsync(entryInfo, closeRule); }
catch (eTop) {
try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) { writeLog("logger fatal failed: " + String(eLog)); }
try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) {}
startRet = { ok: false, err: String(eTop) };
}
var syncInfo = summarizeModuleUpdates(__moduleUpdates);

View File

@@ -1 +1 @@
10291f41f1715ce9182bf463dd728a1f1f9b471e07e179918c6e8e4002fe876c ToolHub.js
458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js

View File

@@ -1195,6 +1195,98 @@ function ToolHubLogger(procInfo) {
ToolHubLogger.prototype._now = function() { return new Date().getTime(); };
ToolHubLogger.prototype._initOnce = function() {
try {
if (FileIO.ensureDir(this.dir)) {
this.initOk = true;
this.cleanupOldFiles();
} else {
this.initOk = false;
this.lastInitErr = "Mkdirs failed: " + this.dir;
}
} catch (e) {
this.initOk = false;
this.lastInitErr = String(e);
}
};
ToolHubLogger.prototype.updateConfig = function(cfg) {
if (!cfg) return;
if (typeof cfg.LOG_KEEP_DAYS === "number") this.keepDays = cfg.LOG_KEEP_DAYS;
if (typeof cfg.LOG_ENABLE !== "undefined") this.enable = !!cfg.LOG_ENABLE;
if (typeof cfg.LOG_DEBUG !== "undefined") this.debug = !!cfg.LOG_DEBUG;
};
ToolHubLogger.prototype._line = function(level, msg) {
var ts = this._now();
var d = new Date(ts);
function pad2(x) { return (x < 10 ? "0" : "") + x; }
var t = d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) +
" " + pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds());
return t + " [" + level + "] " + msg + "\n";
};
ToolHubLogger.prototype._scheduleFlush = function() {
if (this._flushTimer) try { this._flushTimer.cancel(); } catch(e) {}
var self = this;
this._flushTimer = new java.util.Timer();
this._flushTimer.schedule(new java.util.TimerTask({
run: function() { self._flushBuffer(); }
}), 3000); // 3秒后强制刷新
};
ToolHubLogger.prototype._flushBuffer = function() {
if (this._buffer.length === 0) return;
var content = this._buffer.join('');
this._buffer = [];
var path = this.dir + "/" + this.prefix + "_" + this._ymd() + ".log";
FileIO.appendText(path, content);
};
ToolHubLogger.prototype._ymd = function() {
var d = new Date();
return "" + d.getFullYear() +
((d.getMonth() < 9 ? "0" : "") + (d.getMonth() + 1)) +
((d.getDate() < 10 ? "0" : "") + d.getDate());
};
ToolHubLogger.prototype._write = function(level, msg) {
if (!this.enable) return false;
this._buffer.push(this._line(level, msg));
// 缓冲满或错误级别立即写入
if (this._buffer.length >= this._bufferSize || level === 'F' || level === 'E') {
this._flushBuffer();
} else {
this._scheduleFlush(); // 延迟写入
}
return true;
};
ToolHubLogger.prototype.d = function(msg) { if (this.debug) this._write("D", msg); };
ToolHubLogger.prototype.i = function(msg) { this._write("I", msg); };
ToolHubLogger.prototype.w = function(msg) { this._write("W", msg); };
ToolHubLogger.prototype.e = function(msg) { this._write("E", msg); };
ToolHubLogger.prototype.fatal = function(msg) { this._write("F", msg); this._flushBuffer(); };
ToolHubLogger.prototype.cleanupOldFiles = function() {
try {
if (!this.initOk) return false;
var dirF = new java.io.File(this.dir);
var files = dirF.listFiles();
if (!files) return false;
var now = this._now();
var cutoff = now - this.keepDays * 24 * 60 * 60 * 1000;
for (var i = 0; i < files.length; i++) {
var f = files[i];
if (f && f.isFile() && f.getName().indexOf(this.prefix) === 0 && f.lastModified() < cutoff) {
f["delete"]();
}
}
return true;
} catch (e) { return false; };
};
ToolHubLogger.prototype._filePathForToday = function() {
var name = this.prefix + "_" + this._ymd(this._now()) + ".log";
return this.dir + "/" + name;

View File

@@ -1173,20 +1173,683 @@ try { state.root.setVisibility(android.view.View.VISIBLE); } catch(eVis) { safe
api.show(opts);
};
// =======================【工具ShortX 图标库选择器(兼容入口)】======================
// =======================【工具ShortX 图标库选择器【多宫格自适应排列】======================
FloatBallAppWM.prototype.showIconPicker = function(opts) {
var self = this;
var opt = opts || {};
if (typeof self.showShortXIconPickerPopup !== "function") {
try { safeLog(self.L, 'e', "showIconPicker fallback unavailable: showShortXIconPickerPopup missing"); } catch(eLog) {}
return null;
var onPick = (typeof opt.onPick === "function") ? opt.onPick : null;
var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null;
// # 会话隔离
var sessionId = String(new Date().getTime()) + "_" + Math.random().toString(36).substr(2, 9);
var currentSession = sessionId;
this.__currentIconSession = sessionId;
function checkSession() {
if (self.__currentIconSession !== currentSession) return false;
return true;
}
return self.showShortXIconPickerPopup({
currentName: String(opt.currentName || opt.name || ""),
onSelect: function(name) {
if (typeof opt.onPick === "function") opt.onPick(name);
else if (typeof opt.onSelect === "function") opt.onSelect(name);
// # 单例复用(关键修复:跳过已销毁的实例)
try {
if (self.__iconPickerSingleton && typeof self.__iconPickerSingleton.show === "function") {
self.__iconPickerSingleton.show(opts);
return;
}
} catch(eSingle) { safeLog(null, 'e', "catch " + String(eSingle)); }
// # 获取图标列表
var allIcons = [];
try {
allIcons = self.getShortXIconCatalog(true) || [];
} catch(eCatalog) {
safeLog(self.L, 'e', "icon picker catalog failed: " + String(eCatalog));
}
if (!allIcons || allIcons.length === 0) {
self.toast("无法加载 ShortX 图标库");
if (onDismiss) try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); }
return;
}
var Context = android.content.Context;
var wm = context.getSystemService(Context.WINDOW_SERVICE);
// # 状态(新增分页状态)
var state = {
destroyed: false,
hidden: false,
root: null,
params: null,
isAdded: false,
query: "",
filteredIcons: allIcons.slice(),
grid: null,
etSearch: null,
tvStat: null,
scrollView: null,
pagerBar: null, // 分页栏
tvPager: null, // 页码显示
onDismiss: onDismiss,
onDismissCalled: false,
// 分页
currentPage: 0,
itemsPerPage: 24, // 动态计算,这里是默认
totalPages: 1
};
function Li(msg) { try { if (self.L) self.L.i("[iconPicker] " + msg); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } }
function Le(msg) { try { if (self.L) self.L.e("[iconPicker] " + msg); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } }
// # 列数自适应计算
function computeColumns() {
try {
var sw = self.state.screen.w;
var sh = self.state.screen.h;
var isLandscape = sw > sh;
var cellMinDp = isLandscape ? 60 : 68;
var paddingTotalDp = 32;
var availablePx = sw - self.dp(paddingTotalDp);
var cols = Math.floor(availablePx / self.dp(cellMinDp));
if (isLandscape) {
cols = Math.max(6, Math.min(12, cols));
} else {
cols = Math.max(3, Math.min(6, cols));
}
return cols;
} catch(e) { return 4; }
}
// # 计算每页图标数(根据屏幕高度动态计算行数)
function computePageSize() {
try {
var sh = self.state.screen.h;
var maxPanelH = Math.floor(sh * 0.75);
var cols = computeColumns();
// 估算各部分占用的 dp 数
var headerH = 48; // 标题栏
var sepH = 1; // 分隔线
var searchH = 72; // 搜索框(含边距)
var footerH = 30; // 状态栏
var pagerH = 44; // 分页栏
var panelPad = 24; // 面板内边距 top+bottom
var scrollPad = 16; // 滚动区域内边距
var fixedDp = headerH + sepH + searchH + footerH + pagerH + panelPad + scrollPad;
var density = self.state.density || 2.75;
var availableDp = (maxPanelH / density) - fixedDp;
var cellH = 72; // 每个格子大约 72dp图标+文字+边距)
var rows = Math.max(3, Math.floor(availableDp / cellH));
var pageSize = cols * rows;
Li("pageSize calc cols=" + cols + " rows=" + rows + " → " + pageSize + " per page");
return Math.max(12, pageSize);
} catch(e) {
return 24;
}
}
// # 过滤
function filterIcons(q) {
var qLower = "";
try { qLower = String(q || "").toLowerCase(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
if (!qLower) return allIcons.slice();
var out = [];
for (var i = 0; i < allIcons.length; i++) {
var ic = allIcons[i];
if (!ic) continue;
var name = "";
try { name = String(ic.shortName || ic.name || "").toLowerCase(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
if (name.indexOf(qLower) >= 0) out.push(ic);
}
return out;
}
// # 分页:跳到指定页
function goPage(page) {
try {
if (state.totalPages <= 1) return;
if (page < 0) page = 0;
if (page >= state.totalPages) page = state.totalPages - 1;
state.currentPage = page;
buildGrid();
// 滚回顶部
try {
if (state.scrollView) state.scrollView.scrollTo(0, 0);
} catch(eScroll) { safeLog(null, 'e', "catch " + String(eScroll)); }
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
// # 更新统计/页码显示
function updateStat() {
try {
if (state.tvStat) {
state.tvStat.setText("共 " + String(allIcons.length) + " 个 · 当前 " + String(state.filteredIcons.length) + " 个");
}
if (state.tvPager) {
state.tvPager.setText(String(state.currentPage + 1) + " / " + String(state.totalPages));
}
// 更新分页按钮状态
try {
if (state.pagerBar) {
var childCount = state.pagerBar.getChildCount();
for (var i = 0; i < childCount; i++) {
var child = state.pagerBar.getChildAt(i);
if (!child) continue;
try {
var tag = String(child.getTag() || "");
if (tag === "btnPrev") {
child.setEnabled(state.currentPage > 0);
child.setAlpha(state.currentPage > 0 ? 1.0 : 0.35);
} else if (tag === "btnNext") {
child.setEnabled(state.currentPage < state.totalPages - 1);
child.setAlpha(state.currentPage < state.totalPages - 1 ? 1.0 : 0.35);
}
} catch(eTag) { safeLog(null, 'e', "catch " + String(eTag)); }
}
}
} catch(eBar) { safeLog(null, 'e', "catch " + String(eBar)); }
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
// # 构建网格(只渲染当前页)
function buildGrid() {
try {
if (!state.grid) return;
state.grid.removeAllViews();
var cols = computeColumns();
state.grid.setColumnCount(cols);
var isDark = self.isDarkTheme();
var C = self.ui.colors;
var cardColor = isDark ? C.cardDark : C.cardLight;
var textColor = isDark ? C.textPriDark : C.textPriLight;
var cellSizeDp = 56;
var labelSizeSp = 9;
var cellPadDp = 4;
// 计算当前页要显示的图标范围
var startIdx = state.currentPage * state.itemsPerPage;
var endIdx = Math.min(startIdx + state.itemsPerPage, state.filteredIcons.length);
var pageIcons = [];
for (var pi = startIdx; pi < endIdx; pi++) {
pageIcons.push(state.filteredIcons[pi]);
}
for (var i = 0; i < pageIcons.length; i++) {
(function(idx, iconInfo) {
var cell = new android.widget.LinearLayout(context);
cell.setOrientation(android.widget.LinearLayout.VERTICAL);
cell.setGravity(android.view.Gravity.CENTER);
cell.setPadding(self.dp(cellPadDp), self.dp(cellPadDp), self.dp(cellPadDp), self.dp(cellPadDp));
var lp = new android.widget.GridLayout.LayoutParams();
lp.width = 0;
lp.height = android.widget.GridLayout.LayoutParams.WRAP_CONTENT;
lp.columnSpec = android.widget.GridLayout.spec(android.widget.GridLayout.UNDEFINED, 1, 1.0);
lp.setMargins(self.dp(2), self.dp(2), self.dp(2), self.dp(2));
cell.setLayoutParams(lp);
// 图标
var iv = new android.widget.ImageView(context);
try {
var dr = self.resolveShortXDrawable(iconInfo.name);
if (dr) iv.setImageDrawable(dr);
} catch(eIcon) { safeLog(null, 'e', "catch " + String(eIcon)); }
try { iv.setScaleType(android.widget.ImageView.ScaleType.CENTER_INSIDE); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
var ivLp = new android.widget.LinearLayout.LayoutParams(self.dp(cellSizeDp), self.dp(cellSizeDp));
iv.setLayoutParams(ivLp);
cell.addView(iv);
// 文字
var tv = new android.widget.TextView(context);
var label = "";
try { label = String(iconInfo.shortName || iconInfo.name || ""); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
if (label.length > 10) label = label.substring(0, 9) + "…";
tv.setText(label);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, labelSizeSp);
tv.setTextColor(textColor);
tv.setGravity(android.view.Gravity.CENTER);
try { tv.setLines(1); tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
var tvLp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
tvLp.topMargin = self.dp(2);
tv.setLayoutParams(tvLp);
cell.addView(tv);
// 点击效果
try {
var rippleDr = self.ui.createRippleDrawable(cardColor, self.withAlpha(C.primary, 0.2), self.dp(8));
cell.setBackground(rippleDr);
} catch(eRipple) {
cell.setBackground(self.ui.createRoundDrawable(cardColor, self.dp(8)));
}
cell.setClickable(true);
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
try {
self.touchActivity();
if (onPick) onPick(iconInfo.name);
hide();
} catch(eClick) { safeLog(null, 'e', "catch " + String(eClick)); }
}
}));
state.grid.addView(cell);
})(i, pageIcons[i]);
}
updateStat();
} catch(eBuild) {
Le("buildGrid err=" + String(eBuild));
}
}
// # 重新渲染(过滤 + 重置到第1页
function rebuild() {
try {
state.filteredIcons = filterIcons(state.query);
// 重新计算分页
state.itemsPerPage = computePageSize();
state.totalPages = Math.max(1, Math.ceil(state.filteredIcons.length / state.itemsPerPage));
state.currentPage = 0;
buildGrid();
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
// # 隐藏
function hide() {
try {
if (state.destroyed || state.hidden) return;
state.hidden = true;
// 退输入法
try {
if (state.etSearch) {
state.etSearch.clearFocus();
var imm = context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm) imm.hideSoftInputFromWindow(state.etSearch.getWindowToken(), 0);
}
} catch(eK) { safeLog(null, 'e', "catch " + String(eK)); }
// 隐藏 View
try {
if (state.root) state.root.setVisibility(android.view.View.GONE);
} catch(eV) { safeLog(null, 'e', "catch " + String(eV)); }
// 通知外层
try {
if (state.onDismiss && !state.onDismissCalled) {
state.onDismissCalled = true;
state.onDismiss();
}
} catch(eD) { safeLog(null, 'e', "catch " + String(eD)); }
Li("icon picker hidden");
} catch(eHide) { safeLog(null, 'e', "catch " + String(eHide)); }
}
// # 销毁
function destroy() {
try {
if (state.destroyed) return;
state.destroyed = true;
state.hidden = true;
hide();
try {
if (state.isAdded && state.root) {
wm.removeView(state.root);
}
} catch(eR) { safeLog(null, 'e', "catch " + String(eR)); }
state.isAdded = false;
state.root = null;
if (self.__currentIconSession === currentSession) {
self.__currentIconSession = null;
}
try { self.__iconPickerSingleton = null; } catch(eS) { safeLog(null, 'e', "catch " + String(eS)); }
Li("icon picker destroyed");
} catch(eDes) { safeLog(null, 'e', "catch " + String(eDes)); }
}
// # 显示(关键修复:正确处理隐藏后重新显示)
function show() {
if (!checkSession()) return;
if (state.destroyed) return;
// 如果已有实例
if (state.root && state.isAdded) {
if (state.hidden) {
// 之前被隐藏了,重新显示
try {
state.hidden = false;
state.onDismissCalled = false; // 重置 dismiss 标志
state.root.setVisibility(android.view.View.VISIBLE);
// 刷新列数(可能旋转了屏幕)和内容
rebuild();
Li("icon picker re-shown (from hidden)");
return;
} catch(eShow) {
Le("re-show failed: " + String(eShow));
}
} else {
// 已经是显示状态,什么都不做
Li("icon picker already visible");
return;
}
}
// 如果 root 存在但未添加(不应该发生),清掉重建
if (state.root && !state.isAdded) {
try { state.root = null; } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
// ========== 新建面板 ==========
var isDark = self.isDarkTheme();
var C = self.ui.colors;
var bgColor = isDark ? C.bgDark : C.bgLight;
var textColor = isDark ? C.textPriDark : C.textPriLight;
var subTextColor = isDark ? C.textSecDark : C.textSecLight;
var dividerColor = isDark ? C.dividerDark : C.dividerLight;
var inputBgColor = isDark ? C.inputBgDark : C.inputBgLight;
var sw = self.state.screen.w;
var sh = self.state.screen.h;
var panelW = sw - self.dp(32);
var maxPanelH = Math.floor(sh * 0.75);
// --- Root ---
var root = new android.widget.FrameLayout(context);
root.setBackgroundColor(0x00000000);
root.setOnTouchListener(new JavaAdapter(android.view.View.OnTouchListener, {
onTouch: function(v, e) {
self.touchActivity();
if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) {
try {
var rect = new android.graphics.Rect();
if (state.root) {
state.root.getGlobalVisibleRect(rect);
var x = e.getRawX();
var y = e.getRawY();
if (!rect.contains(x, y)) {
hide();
}
}
} catch(eOut) { safeLog(null, 'e', "catch " + String(eOut)); }
}
return false;
}
}));
// --- 面板容器 ---
var panel = new android.widget.LinearLayout(context);
panel.setOrientation(android.widget.LinearLayout.VERTICAL);
var bgDr = new android.graphics.drawable.GradientDrawable();
bgDr.setColor(bgColor);
bgDr.setCornerRadius(self.dp(16));
panel.setBackground(bgDr);
try { panel.setElevation(self.dp(8)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
panel.setPadding(self.dp(16), self.dp(12), self.dp(16), self.dp(12));
var panelLp = new android.widget.FrameLayout.LayoutParams(panelW, android.widget.FrameLayout.LayoutParams.WRAP_CONTENT);
panelLp.gravity = android.view.Gravity.CENTER;
root.addView(panel, panelLp);
// --- 标题栏 ---
var header = new android.widget.LinearLayout(context);
header.setOrientation(android.widget.LinearLayout.HORIZONTAL);
header.setGravity(android.view.Gravity.CENTER_VERTICAL);
var tvTitle = new android.widget.TextView(context);
tvTitle.setText("选择图标");
tvTitle.setTextColor(textColor);
tvTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16);
tvTitle.setTypeface(null, android.graphics.Typeface.BOLD);
header.addView(tvTitle);
var spacer = new android.widget.LinearLayout(context);
spacer.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, 0, 1));
header.addView(spacer);
var btnClose = new android.widget.TextView(context);
btnClose.setText("✕");
btnClose.setTextColor(subTextColor);
btnClose.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18);
btnClose.setPadding(self.dp(8), self.dp(4), self.dp(4), self.dp(4));
btnClose.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() { hide(); }
}));
header.addView(btnClose);
panel.addView(header);
// --- 分隔线 ---
var sep = new android.view.View(context);
sep.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(1)
));
sep.setBackgroundColor(dividerColor);
panel.addView(sep);
// --- 搜索框 ---
var searchBox = new android.widget.LinearLayout(context);
searchBox.setOrientation(android.widget.LinearLayout.HORIZONTAL);
searchBox.setGravity(android.view.Gravity.CENTER_VERTICAL);
searchBox.setBackground(self.ui.createRoundDrawable(inputBgColor, self.dp(8)));
searchBox.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(6));
var searchLp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
searchLp.setMargins(0, self.dp(8), 0, self.dp(8));
searchBox.setLayoutParams(searchLp);
var et = new android.widget.EditText(context);
et.setHint("搜索图标名称...");
try { et.setHintTextColor(subTextColor); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
et.setTextColor(textColor);
et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
et.setBackground(null);
et.setSingleLine(true);
var etLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1);
et.setLayoutParams(etLp);
et.addTextChangedListener(new JavaAdapter(android.text.TextWatcher, {
beforeTextChanged: function(s, start, count, after) {},
onTextChanged: function(s, start, before, count) {},
afterTextChanged: function(s) {
try {
state.query = String(s || "");
rebuild();
} catch(eTxt) { safeLog(null, 'e', "catch " + String(eTxt)); }
}
}));
searchBox.addView(et);
state.etSearch = et;
var btnClear = new android.widget.TextView(context);
btnClear.setText("✕");
btnClear.setTextColor(subTextColor);
btnClear.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
btnClear.setPadding(self.dp(6), self.dp(2), self.dp(2), self.dp(2));
btnClear.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
try {
et.setText("");
state.query = "";
rebuild();
} catch(eClr) { safeLog(null, 'e', "catch " + String(eClr)); }
}
}));
searchBox.addView(btnClear);
panel.addView(searchBox);
// --- 滚动区域 ---
var scroll = new android.widget.ScrollView(context);
try { scroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
try { scroll.setVerticalScrollBarEnabled(false); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
var grid = new android.widget.GridLayout(context);
state.grid = grid;
scroll.addView(grid);
var scrollLp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0
);
scrollLp.weight = 1;
scroll.setLayoutParams(scrollLp);
panel.addView(scroll);
state.scrollView = scroll;
// --- 底部:状态栏 + 分页栏 ---
// 状态栏
var footer = new android.widget.LinearLayout(context);
footer.setOrientation(android.widget.LinearLayout.HORIZONTAL);
footer.setGravity(android.view.Gravity.CENTER_VERTICAL);
footer.setPadding(0, self.dp(4), 0, self.dp(4));
var tvStat = new android.widget.TextView(context);
tvStat.setTextColor(subTextColor);
tvStat.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
footer.addView(tvStat);
state.tvStat = tvStat;
panel.addView(footer);
// 分页栏
var pagerBar = new android.widget.LinearLayout(context);
pagerBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
pagerBar.setGravity(android.view.Gravity.CENTER);
pagerBar.setPadding(0, self.dp(2), 0, 0);
var pagerLp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
pagerBar.setLayoutParams(pagerLp);
// 分页按钮工厂
function makePagerBtn(text, tag, onClick) {
var btn = new android.widget.TextView(context);
btn.setText(text);
btn.setTag(tag);
btn.setTextColor(C.primary);
btn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
btn.setTypeface(null, android.graphics.Typeface.BOLD);
btn.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6));
try {
var btnBg = self.ui.createRoundDrawable(
self.withAlpha(C.primary, 0.08),
self.dp(6)
);
btn.setBackground(btnBg);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
btn.setClickable(true);
btn.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
try { self.touchActivity(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
onClick();
}
}));
return btn;
}
var btnPrev = makePagerBtn("◀ 上一页", "btnPrev", function() {
goPage(state.currentPage - 1);
});
pagerBar.addView(btnPrev);
// 页码
var tvPager = new android.widget.TextView(context);
tvPager.setText("1 / 1");
tvPager.setTextColor(subTextColor);
tvPager.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tvPager.setPadding(self.dp(16), self.dp(4), self.dp(16), self.dp(4));
pagerBar.addView(tvPager);
state.tvPager = tvPager;
var btnNext = makePagerBtn("下一页 ▶", "btnNext", function() {
goPage(state.currentPage + 1);
});
pagerBar.addView(btnNext);
panel.addView(pagerBar);
state.pagerBar = pagerBar;
// --- 添加到 WM ---
var lp = new android.view.WindowManager.LayoutParams(
android.view.WindowManager.LayoutParams.MATCH_PARENT,
android.view.WindowManager.LayoutParams.MATCH_PARENT,
android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND,
android.graphics.PixelFormat.TRANSLUCENT
);
lp.dimAmount = 0.5;
lp.gravity = android.view.Gravity.TOP | android.view.Gravity.START;
lp.x = 0;
lp.y = 0;
try {
wm.addView(root, lp);
state.isAdded = true;
} catch(eAdd) {
Le("addView failed: " + String(eAdd));
self.toast("图标选择器打开失败");
if (onDismiss) try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); }
return;
}
state.root = root;
state.params = lp;
// 动画
try {
panel.setScaleX(0.95);
panel.setScaleY(0.95);
panel.setAlpha(0);
panel.animate()
.scaleX(1)
.scaleY(1)
.alpha(1)
.setDuration(180)
.setInterpolator(new android.view.animation.AccelerateDecelerateInterpolator())
.start();
} catch(eA) { safeLog(null, 'e', "catch " + String(eA)); }
// 初始渲染(会计算分页)
rebuild();
Li("icon picker shown icons=" + String(allIcons.length));
}
// 入口(关键修复:不在 api.show 中提前设置 state.hidden让 show() 自己判断)
var api = {
show: function(newOpts) {
try {
var o = newOpts || {};
onPick = (typeof o.onPick === "function") ? o.onPick : onPick;
onDismiss = (typeof o.onDismiss === "function") ? o.onDismiss : onDismiss;
state.onDismiss = onDismiss;
state.onDismissCalled = false;
} catch(eOpt) { safeLog(null, 'e', "catch " + String(eOpt)); }
// 不在这里设置 state.hidden = false让内部 show() 自己判断是隐藏重显还是新建
show();
},
onDismiss: (typeof opt.onDismiss === "function") ? opt.onDismiss : null
});
hide: hide,
destroy: destroy
};
try { self.__iconPickerSingleton = api; } catch(eSet) { safeLog(null, 'e', "catch " + String(eSet)); }
api.show(opts);
};

View File

@@ -127,7 +127,7 @@ 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) { safeLog(this.L, 'e', "unregisterPanelPredictiveBack before remove failed: " + String(eBack)); }
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 };
@@ -283,7 +283,7 @@ FloatBallAppWM.prototype.hidePanelPredictiveBackIndicator = function() {
try {
var v = this.state.predictiveBackIndicatorView;
if (v && this.state.wm) {
try { this.state.wm.removeView(v); } catch (eRm) { safeLog(this.L, 'e', "safeRemoveView removeView failed: " + String(eRm)); }
try { this.state.wm.removeView(v); } catch (eRm) {}
}
this.state.predictiveBackIndicatorView = null;
this.state.predictiveBackIndicatorLp = null;
@@ -398,7 +398,7 @@ FloatBallAppWM.prototype.unregisterPanelPredictiveBack = function(panel) {
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) { safeLog(this.L, 'e', "panel predictive unregister failed: " + String(eUnreg)); }
try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) {}
} else {
kept.push(it);
}
@@ -993,7 +993,7 @@ FloatBallAppWM.prototype.scheduleScreenReflow = function(reason) {
}
}), 260);
} catch (e0) {
try { this.onScreenChangedReflow(reason); } catch(e1) { safeLog(this.L, 'e', "onScreenChangedReflow failed: " + String(e1)); }
try { this.onScreenChangedReflow(reason); } catch(e1) {}
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ function runOnMainSync(fn, timeoutMs) {
if (mainLooper !== null && myLooper !== null && myLooper === mainLooper) {
return { ok: true, value: fn() };
}
} catch (eLoop) { safeLog(this.L, 'e', "runOnMainSync looper check failed: " + String(eLoop)); }
} catch (eLoop) {}
try {
var box = { ok: false, value: null, error: null };
@@ -109,7 +109,7 @@ FloatBallAppWM.prototype.close = function() {
if (android.os.Build.VERSION.SDK_INT >= 18) this.state.ht.quitSafely();
else this.state.ht.quit();
}
} catch (eQ) { safeLog(this.L, 'e', "shortcut picker singleton destroy failed: " + String(eQ)); }
} catch (eQ) {}
// # 清理图标加载线程
try {
@@ -117,13 +117,13 @@ FloatBallAppWM.prototype.close = function() {
if (android.os.Build.VERSION.SDK_INT >= 18) this._iconLoader.ht.quitSafely();
else this._iconLoader.ht.quit();
}
} catch (eIcon) { safeLog(this.L, 'e', "icon picker singleton destroy failed: " + String(eIcon)); }
} catch (eIcon) {}
try {
if (this.__scIconLoaderSingleton && this.__scIconLoaderSingleton.ht) {
if (android.os.Build.VERSION.SDK_INT >= 18) this.__scIconLoaderSingleton.ht.quitSafely();
else this.__scIconLoaderSingleton.ht.quit();
}
} catch (eScIcon) { safeLog(this.L, 'e', "shortcut icon loader singleton destroy failed: " + String(eScIcon)); }
} catch (eScIcon) {}
try { this.__scIconLoaderSingleton = null; } catch (eScIcon2) {}
safeLog(this.L, 'i', "close done");
@@ -211,7 +211,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
var closeRcv = registerReceiverOnMain(this.config.ACTION_CLOSE_ALL, function(ctx, it) {
try {
h.post(new JavaAdapter(java.lang.Runnable, {
run: function() { try { self.close(); } catch (e1) { safeLog(self.L, 'e', "close broadcast handler failed: " + String(e1)); } }
run: function() { try { self.close(); } catch (e1) {} }
}));
} catch (e2) {}
});
@@ -250,7 +250,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
var reason = "";
try { reason = String(intent.getStringExtra("reason") || ""); } catch (eReason) { reason = ""; }
h.post(new JavaAdapter(java.lang.Runnable, {
run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) { safeLog(self.L, 'e', "system ui dismiss handler failed: " + String(eSysDlg)); } }
run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) {} }
}));
} catch (eSysDlgOuter) {}
});
@@ -282,10 +282,10 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
} catch (eAdd) {
startBox.ok = false;
startBox.err = "悬浮球 addView 失败: " + String(eAdd);
try { self.toast(startBox.err); } catch (eT) { safeLog(self.L, 'e', "toast start error failed: " + String(eT)); }
try { self.toast(startBox.err); } catch (eT) {}
if (self.L) self.L.fatal("addView ball fail err=" + String(eAdd));
self.state.addedBall = false;
try { self.close(); } catch (eC) { safeLog(self.L, 'e', "close after start failure failed: " + String(eC)); }
try { self.close(); } catch (eC) {}
return;
}

View File

@@ -2,8 +2,8 @@
"alg": "SHA256withRSA",
"files": {
"th_01_base.js": {
"sha256": "d4b409e42d3b3d5b3b107c4b2338fe83ad9b7d6d7f7f6bc0bbc9e28a94a866cd",
"size": 53253
"sha256": "6b7a51c97f76b797b176c8fcf712dbafb8d33ebab448d0c7574f26568dcd14df",
"size": 56392
},
"th_02_core.js": {
"sha256": "15bb9bfbd19a673d442e221b0a00a456ed5f87af2666b9c73b117d6223faeecd",
@@ -26,16 +26,16 @@
"size": 22909
},
"th_07_shortcut.js": {
"sha256": "f992dcd6dd7993690d789e2a04b92d4e4be96b708a1a21920c5e8038f2d814c8",
"size": 48962
"sha256": "7b2dbd1e35c636cca4ccce335dfb9e0b972342972ce012116ff4bbcfc438caa1",
"size": 72992
},
"th_08_content.js": {
"sha256": "8a76f15dfd1292081cba4b2dd218424be66540350e2807065421a6176a86c2db",
"size": 7938
},
"th_09_animation.js": {
"sha256": "5e0e70319c13c39674b3524e30d6ade5c4188ad4f52f331c9bee83193580d0cd",
"size": 40549
"sha256": "2a991cbf4bf38bfb3d1dda876c0c86778e5dbbe75d6eb268a068e3f200ce0c13",
"size": 40229
},
"th_10_shell.js": {
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
@@ -54,23 +54,19 @@
"size": 21198
},
"th_14_panels.js": {
"sha256": "611c00c8362c346ff0a674aa29cd98e6240cbbe36a2d6c171e0188996bca22a5",
"size": 213491
"sha256": "185d7ed9d85ffa7e31b70abbd425c0d79d6a435a4c5cb6dc7610d942fdc0fc87",
"size": 262019
},
"th_15_extra.js": {
"sha256": "1aa3bbe9c99e047cfaf663f17501d87bba1b5810a8002e66bca1f6ea605371a4",
"size": 99378
},
"th_15_picker.js": {
"sha256": "9d59acbbee0db6058e2eba2db310f75b581cbd84d1b8fdaa48f13e244f1cd4f8",
"size": 48989
},
"th_16_entry.js": {
"sha256": "9b42884503be328c19a8f95e9e9ff986b1938586909bcf515971f3ff881133be",
"size": 13423
"sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1",
"size": 12799
}
},
"keyId": "toolhub-targets-2026-rsa3072",
"schema": 2,
"version": 20260521154208
"version": 20260521161017
}

View File

@@ -1 +1 @@
GfMow5N7dSXGnk4mFX5ENNdLfimG1M2lzV6r3Eur0YpHibkk0p8/Ghz8HnwN5a4sAs7caGEm0msdtbLV4UphuVJoYNUsH2qML7umAnUTyvbgZ0PqV5h0Svw7XXpzYoH9JiHM8bthzKxNqBtftKRFiAIC76Z987WmU5zfflAONWD/LPQixbGndB0CqbubxoATkBzERDb1u5YmqajkfnqPJFuuzhVamfqrTA4UZUbAuwiL48QsABEuiUZdpDa/HVtIEJ+1wO9/OtAhLGOscoNlUJz1OCffSowJ32OUMpwJJHKBmOmVY9S2x+WHOHKE7psuB8DLoVSslsLKlhL76pdblTyMJL/g76GpEx7y/CIoUs0x7ayBsytwl6yCYineyd37PYpNZql+PJ0l9pcDr2gkI54ltxam5z3cWCKAIICfm//Y4R4nEk/GbLI2oXKHoHMskSRedYnIrqH64FUHj6KzcAn+S+uXw91OCaXPM4in0D2oc1C3SKUHtSHM27YQYC6a
OmNJawXdMOyamvJ6qAytud+LHrHchf22fFqWsU6p2UVUavGCGsv4XbNcmvDl7dT85Q9kOT4Qpb68mE8pnYQ3foQ9wYToD2aEooVnjwpCoX5JJmZwxdyR2PQhfgY7e2GNYJ6IubHweZTRZsRYKnMxlA5x8EnaF5kvK+YoCt0dulaMVQS0KK3tpuwlb13WxA8C1256hrGQaZ0R4lptEsOqjRB1r+lk3r27omHEI4rwcxrHmqJ19aOTLKP/9d8xImg4HAI/qeaLDDpfyKkKhQBl4TCp5Sj0KO8Noy48dSbsW7QS5VKKugo0unA5rBKch1QliCNiHF6moK8nVERvAHQLPHbpJvQI78E8rQW0Q632FZUalLOURldsTizsCr9YIZTeB6xumY8h5Ufevpnz9xNmyqUvmvQ8tZ4D5+qn14FngT+WoK6y65DTWjA37UICx5pqqLA4KRyGKuTHDXpaHurzArN4B1OnnDvqgru0GnpYWgfJZTnLBqqpPlBcS56lvYXo

View File

@@ -28,7 +28,7 @@ MODULES = [
"th_05_persistence.js", "th_06_icon_parser.js", "th_07_shortcut.js",
"th_08_content.js", "th_09_animation.js", "th_10_shell.js",
"th_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js",
"th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js",
"th_14_panels.js", "th_15_extra.js", "th_16_entry.js",
]