refactor: split picker module and improve diagnostics
This commit is contained in:
26
ToolHub.js
26
ToolHub.js
@@ -42,7 +42,9 @@ function writeLog(msg) {
|
|||||||
var writer = new java.io.FileWriter(f, true);
|
var writer = new java.io.FileWriter(f, true);
|
||||||
writer.write("[" + ts + "] " + String(msg) + "\n");
|
writer.write("[" + ts + "] " + String(msg) + "\n");
|
||||||
writer.close();
|
writer.close();
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
try { android.util.Log.e("ToolHub", "writeLog failed: " + String(e)); } catch(eLog) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runShell(cmdArr) {
|
function runShell(cmdArr) {
|
||||||
@@ -50,7 +52,7 @@ function runShell(cmdArr) {
|
|||||||
var proc = java.lang.Runtime.getRuntime().exec(cmdArr);
|
var proc = java.lang.Runtime.getRuntime().exec(cmdArr);
|
||||||
proc.waitFor();
|
proc.waitFor();
|
||||||
return proc.exitValue() === 0;
|
return proc.exitValue() === 0;
|
||||||
} catch (e) { return false; }
|
} catch (e) { writeLog("runShell failed: " + String(e)); return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDirPerms(path) {
|
function checkDirPerms(path) {
|
||||||
@@ -64,7 +66,7 @@ function checkDirPerms(path) {
|
|||||||
var parts = String(line).trim().split(/\s+/);
|
var parts = String(line).trim().split(/\s+/);
|
||||||
if (parts.length >= 3) return String(parts[0]) === "1000" && String(parts[1]) === "1000" && String(parts[2]) === "700";
|
if (parts.length >= 3) return String(parts[0]) === "1000" && String(parts[1]) === "1000" && String(parts[2]) === "700";
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) { writeLog("checkDirPerms failed: " + String(e)); }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ function readTextFile(path) {
|
|||||||
while ((line = r.readLine()) != null) sb.append(line).append("\n");
|
while ((line = r.readLine()) != null) sb.append(line).append("\n");
|
||||||
r.close();
|
r.close();
|
||||||
return String(sb.toString());
|
return String(sb.toString());
|
||||||
} catch (e) { return null; }
|
} catch (e) { writeLog("readTextFile failed: " + String(path) + " err=" + String(e)); return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeTextFile(path, text) {
|
function writeTextFile(path, text) {
|
||||||
@@ -112,7 +114,7 @@ function writeTextFile(path, text) {
|
|||||||
w.write(String(text));
|
w.write(String(text));
|
||||||
w.close();
|
w.close();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) { return false; }
|
} catch (e) { writeLog("writeTextFile failed: " + String(path) + " err=" + String(e)); return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function readFirstLine(path) {
|
function readFirstLine(path) {
|
||||||
@@ -284,15 +286,15 @@ function ensureVerifiedModule(relPath, destFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tmpFile = new java.io.File(destFile.getAbsolutePath() + ".tmp");
|
var tmpFile = new java.io.File(destFile.getAbsolutePath() + ".tmp");
|
||||||
try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) {}
|
try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) { writeLog("tmp delete before download failed: " + String(eDelTmp0)); }
|
||||||
var size = downloadFile(GIT_BASE + relPath, tmpFile);
|
var size = downloadFile(GIT_BASE + relPath, tmpFile);
|
||||||
if (expectedSize > 0 && size !== expectedSize) {
|
if (expectedSize > 0 && size !== expectedSize) {
|
||||||
try { tmpFile.delete(); } catch (eDelSize) {}
|
try { tmpFile.delete(); } catch (eDelSize) { writeLog("tmp delete after size mismatch failed: " + String(eDelSize)); }
|
||||||
throw "manifest size mismatch for " + relPath + ": expected=" + expectedSize + ", got=" + size;
|
throw "manifest size mismatch for " + relPath + ": expected=" + expectedSize + ", got=" + size;
|
||||||
}
|
}
|
||||||
var tmpHash = sha256File(tmpFile.getAbsolutePath());
|
var tmpHash = sha256File(tmpFile.getAbsolutePath());
|
||||||
if (!tmpHash || String(tmpHash).toLowerCase() !== expectedHash) {
|
if (!tmpHash || String(tmpHash).toLowerCase() !== expectedHash) {
|
||||||
try { tmpFile.delete(); } catch (eDelHash) {}
|
try { tmpFile.delete(); } catch (eDelHash) { writeLog("tmp delete after hash mismatch failed: " + String(eDelHash)); }
|
||||||
throw "manifest SHA256 mismatch for " + relPath + ": expected=" + expectedHash + ", actual=" + tmpHash;
|
throw "manifest SHA256 mismatch for " + relPath + ": expected=" + expectedHash + ", actual=" + tmpHash;
|
||||||
}
|
}
|
||||||
var wasNew = !destFile.exists();
|
var wasNew = !destFile.exists();
|
||||||
@@ -333,7 +335,7 @@ function loadScript(relPath) {
|
|||||||
geval(String(code));
|
geval(String(code));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
var errMsg = "loadScript(" + relPath + ") failed: " + e;
|
var errMsg = "loadScript(" + relPath + ") failed: " + e;
|
||||||
try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) {}
|
try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) { writeLog("android log failed: " + String(eLog)); }
|
||||||
throw errMsg;
|
throw errMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,7 +343,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",
|
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_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_10_shell.js", "th_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js",
|
||||||
"th_14_panels.js", "th_15_extra.js", "th_16_entry.js"];
|
"th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js"];
|
||||||
var __moduleUpdates = [];
|
var __moduleUpdates = [];
|
||||||
var loadErrors = [];
|
var loadErrors = [];
|
||||||
var criticalModules = { "th_01_base.js": true, "th_16_entry.js": true };
|
var criticalModules = { "th_01_base.js": true, "th_16_entry.js": true };
|
||||||
@@ -353,7 +355,7 @@ for (var i = 0; i < modules.length; i++) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
var modErr = "Module load failed: " + modules[i] + " -> " + String(e);
|
var modErr = "Module load failed: " + modules[i] + " -> " + String(e);
|
||||||
writeLog(modErr);
|
writeLog(modErr);
|
||||||
try { android.util.Log.e("ToolHub", modErr); } catch(eLog) {}
|
try { android.util.Log.e("ToolHub", modErr); } catch(eLog) { writeLog("android log failed: " + String(eLog)); }
|
||||||
loadErrors.push({ module: modules[i], err: String(e) });
|
loadErrors.push({ module: modules[i], err: String(e) });
|
||||||
if (criticalModules[modules[i]]) throw "Critical module failed: " + modules[i] + " (" + String(e) + ")";
|
if (criticalModules[modules[i]]) throw "Critical module failed: " + modules[i] + " (" + String(e) + ")";
|
||||||
}
|
}
|
||||||
@@ -404,7 +406,7 @@ var __out = (function() {
|
|||||||
var startRet = null;
|
var startRet = null;
|
||||||
try { startRet = app.startAsync(entryInfo, closeRule); }
|
try { startRet = app.startAsync(entryInfo, closeRule); }
|
||||||
catch (eTop) {
|
catch (eTop) {
|
||||||
try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) {}
|
try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) { writeLog("logger fatal failed: " + String(eLog)); }
|
||||||
startRet = { ok: false, err: String(eTop) };
|
startRet = { ok: false, err: String(eTop) };
|
||||||
}
|
}
|
||||||
var syncInfo = summarizeModuleUpdates(__moduleUpdates);
|
var syncInfo = summarizeModuleUpdates(__moduleUpdates);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js
|
10291f41f1715ce9182bf463dd728a1f1f9b471e07e179918c6e8e4002fe876c ToolHub.js
|
||||||
|
|||||||
@@ -1195,98 +1195,6 @@ function ToolHubLogger(procInfo) {
|
|||||||
|
|
||||||
ToolHubLogger.prototype._now = function() { return new Date().getTime(); };
|
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() {
|
ToolHubLogger.prototype._filePathForToday = function() {
|
||||||
var name = this.prefix + "_" + this._ymd(this._now()) + ".log";
|
var name = this.prefix + "_" + this._ymd(this._now()) + ".log";
|
||||||
return this.dir + "/" + name;
|
return this.dir + "/" + name;
|
||||||
|
|||||||
@@ -1173,683 +1173,20 @@ try { state.root.setVisibility(android.view.View.VISIBLE); } catch(eVis) { safe
|
|||||||
api.show(opts);
|
api.show(opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
// =======================【工具:ShortX 图标库选择器【多宫格自适应排列】======================
|
// =======================【工具:ShortX 图标库选择器(兼容入口)】======================
|
||||||
FloatBallAppWM.prototype.showIconPicker = function(opts) {
|
FloatBallAppWM.prototype.showIconPicker = function(opts) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var opt = opts || {};
|
var opt = opts || {};
|
||||||
var onPick = (typeof opt.onPick === "function") ? opt.onPick : null;
|
if (typeof self.showShortXIconPickerPopup !== "function") {
|
||||||
var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null;
|
try { safeLog(self.L, 'e', "showIconPicker fallback unavailable: showShortXIconPickerPopup missing"); } catch(eLog) {}
|
||||||
|
return 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 || ""),
|
||||||
try {
|
onSelect: function(name) {
|
||||||
if (self.__iconPickerSingleton && typeof self.__iconPickerSingleton.show === "function") {
|
if (typeof opt.onPick === "function") opt.onPick(name);
|
||||||
self.__iconPickerSingleton.show(opts);
|
else if (typeof opt.onSelect === "function") opt.onSelect(name);
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
hide: hide,
|
onDismiss: (typeof opt.onDismiss === "function") ? opt.onDismiss : null
|
||||||
destroy: destroy
|
});
|
||||||
};
|
|
||||||
try { self.__iconPickerSingleton = api; } catch(eSet) { safeLog(null, 'e', "catch " + String(eSet)); }
|
|
||||||
api.show(opts);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ 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 (this.unregisterPanelPredictiveBack) this.unregisterPanelPredictiveBack(v); } catch (eBack) { safeLog(this.L, 'e', "unregisterPanelPredictiveBack before remove failed: " + String(eBack)); }
|
||||||
try { if (whichName === "viewerPanel" && this.state && String(this.state.viewerPanelType || "") === "tool_app" && this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
|
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 };
|
||||||
@@ -283,7 +283,7 @@ FloatBallAppWM.prototype.hidePanelPredictiveBackIndicator = function() {
|
|||||||
try {
|
try {
|
||||||
var v = this.state.predictiveBackIndicatorView;
|
var v = this.state.predictiveBackIndicatorView;
|
||||||
if (v && this.state.wm) {
|
if (v && this.state.wm) {
|
||||||
try { this.state.wm.removeView(v); } catch (eRm) {}
|
try { this.state.wm.removeView(v); } catch (eRm) { safeLog(this.L, 'e', "safeRemoveView removeView failed: " + String(eRm)); }
|
||||||
}
|
}
|
||||||
this.state.predictiveBackIndicatorView = null;
|
this.state.predictiveBackIndicatorView = null;
|
||||||
this.state.predictiveBackIndicatorLp = null;
|
this.state.predictiveBackIndicatorLp = null;
|
||||||
@@ -398,7 +398,7 @@ FloatBallAppWM.prototype.unregisterPanelPredictiveBack = function(panel) {
|
|||||||
for (var i = 0; i < entries.length; i++) {
|
for (var i = 0; i < entries.length; i++) {
|
||||||
var it = entries[i];
|
var it = entries[i];
|
||||||
if (!it || it.view === panel) {
|
if (!it || it.view === panel) {
|
||||||
try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) {}
|
try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) { safeLog(this.L, 'e', "panel predictive unregister failed: " + String(eUnreg)); }
|
||||||
} else {
|
} else {
|
||||||
kept.push(it);
|
kept.push(it);
|
||||||
}
|
}
|
||||||
@@ -993,7 +993,7 @@ FloatBallAppWM.prototype.scheduleScreenReflow = function(reason) {
|
|||||||
}
|
}
|
||||||
}), 260);
|
}), 260);
|
||||||
} catch (e0) {
|
} catch (e0) {
|
||||||
try { this.onScreenChangedReflow(reason); } catch(e1) {}
|
try { this.onScreenChangedReflow(reason); } catch(e1) { safeLog(this.L, 'e', "onScreenChangedReflow failed: " + String(e1)); }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1156
code/th_14_panels.js
1156
code/th_14_panels.js
File diff suppressed because it is too large
Load Diff
1109
code/th_15_picker.js
Normal file
1109
code/th_15_picker.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ function runOnMainSync(fn, timeoutMs) {
|
|||||||
if (mainLooper !== null && myLooper !== null && myLooper === mainLooper) {
|
if (mainLooper !== null && myLooper !== null && myLooper === mainLooper) {
|
||||||
return { ok: true, value: fn() };
|
return { ok: true, value: fn() };
|
||||||
}
|
}
|
||||||
} catch (eLoop) {}
|
} catch (eLoop) { safeLog(this.L, 'e', "runOnMainSync looper check failed: " + String(eLoop)); }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var box = { ok: false, value: null, error: null };
|
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();
|
if (android.os.Build.VERSION.SDK_INT >= 18) this.state.ht.quitSafely();
|
||||||
else this.state.ht.quit();
|
else this.state.ht.quit();
|
||||||
}
|
}
|
||||||
} catch (eQ) {}
|
} catch (eQ) { safeLog(this.L, 'e', "shortcut picker singleton destroy failed: " + String(eQ)); }
|
||||||
|
|
||||||
// # 清理图标加载线程
|
// # 清理图标加载线程
|
||||||
try {
|
try {
|
||||||
@@ -117,13 +117,13 @@ FloatBallAppWM.prototype.close = function() {
|
|||||||
if (android.os.Build.VERSION.SDK_INT >= 18) this._iconLoader.ht.quitSafely();
|
if (android.os.Build.VERSION.SDK_INT >= 18) this._iconLoader.ht.quitSafely();
|
||||||
else this._iconLoader.ht.quit();
|
else this._iconLoader.ht.quit();
|
||||||
}
|
}
|
||||||
} catch (eIcon) {}
|
} catch (eIcon) { safeLog(this.L, 'e', "icon picker singleton destroy failed: " + String(eIcon)); }
|
||||||
try {
|
try {
|
||||||
if (this.__scIconLoaderSingleton && this.__scIconLoaderSingleton.ht) {
|
if (this.__scIconLoaderSingleton && this.__scIconLoaderSingleton.ht) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= 18) this.__scIconLoaderSingleton.ht.quitSafely();
|
if (android.os.Build.VERSION.SDK_INT >= 18) this.__scIconLoaderSingleton.ht.quitSafely();
|
||||||
else this.__scIconLoaderSingleton.ht.quit();
|
else this.__scIconLoaderSingleton.ht.quit();
|
||||||
}
|
}
|
||||||
} catch (eScIcon) {}
|
} catch (eScIcon) { safeLog(this.L, 'e', "shortcut icon loader singleton destroy failed: " + String(eScIcon)); }
|
||||||
try { this.__scIconLoaderSingleton = null; } catch (eScIcon2) {}
|
try { this.__scIconLoaderSingleton = null; } catch (eScIcon2) {}
|
||||||
|
|
||||||
safeLog(this.L, 'i', "close done");
|
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) {
|
var closeRcv = registerReceiverOnMain(this.config.ACTION_CLOSE_ALL, function(ctx, it) {
|
||||||
try {
|
try {
|
||||||
h.post(new JavaAdapter(java.lang.Runnable, {
|
h.post(new JavaAdapter(java.lang.Runnable, {
|
||||||
run: function() { try { self.close(); } catch (e1) {} }
|
run: function() { try { self.close(); } catch (e1) { safeLog(self.L, 'e', "close broadcast handler failed: " + String(e1)); } }
|
||||||
}));
|
}));
|
||||||
} catch (e2) {}
|
} catch (e2) {}
|
||||||
});
|
});
|
||||||
@@ -250,7 +250,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
|
|||||||
var reason = "";
|
var reason = "";
|
||||||
try { reason = String(intent.getStringExtra("reason") || ""); } catch (eReason) { reason = ""; }
|
try { reason = String(intent.getStringExtra("reason") || ""); } catch (eReason) { reason = ""; }
|
||||||
h.post(new JavaAdapter(java.lang.Runnable, {
|
h.post(new JavaAdapter(java.lang.Runnable, {
|
||||||
run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) {} }
|
run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) { safeLog(self.L, 'e', "system ui dismiss handler failed: " + String(eSysDlg)); } }
|
||||||
}));
|
}));
|
||||||
} catch (eSysDlgOuter) {}
|
} catch (eSysDlgOuter) {}
|
||||||
});
|
});
|
||||||
@@ -282,10 +282,10 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
|
|||||||
} catch (eAdd) {
|
} catch (eAdd) {
|
||||||
startBox.ok = false;
|
startBox.ok = false;
|
||||||
startBox.err = "悬浮球 addView 失败: " + String(eAdd);
|
startBox.err = "悬浮球 addView 失败: " + String(eAdd);
|
||||||
try { self.toast(startBox.err); } catch (eT) {}
|
try { self.toast(startBox.err); } catch (eT) { safeLog(self.L, 'e', "toast start error failed: " + String(eT)); }
|
||||||
if (self.L) self.L.fatal("addView ball fail err=" + String(eAdd));
|
if (self.L) self.L.fatal("addView ball fail err=" + String(eAdd));
|
||||||
self.state.addedBall = false;
|
self.state.addedBall = false;
|
||||||
try { self.close(); } catch (eC) {}
|
try { self.close(); } catch (eC) { safeLog(self.L, 'e', "close after start failure failed: " + String(eC)); }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"alg": "SHA256withRSA",
|
"alg": "SHA256withRSA",
|
||||||
"files": {
|
"files": {
|
||||||
"th_01_base.js": {
|
"th_01_base.js": {
|
||||||
"sha256": "6b7a51c97f76b797b176c8fcf712dbafb8d33ebab448d0c7574f26568dcd14df",
|
"sha256": "d4b409e42d3b3d5b3b107c4b2338fe83ad9b7d6d7f7f6bc0bbc9e28a94a866cd",
|
||||||
"size": 56392
|
"size": 53253
|
||||||
},
|
},
|
||||||
"th_02_core.js": {
|
"th_02_core.js": {
|
||||||
"sha256": "15bb9bfbd19a673d442e221b0a00a456ed5f87af2666b9c73b117d6223faeecd",
|
"sha256": "15bb9bfbd19a673d442e221b0a00a456ed5f87af2666b9c73b117d6223faeecd",
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
"size": 22909
|
"size": 22909
|
||||||
},
|
},
|
||||||
"th_07_shortcut.js": {
|
"th_07_shortcut.js": {
|
||||||
"sha256": "7b2dbd1e35c636cca4ccce335dfb9e0b972342972ce012116ff4bbcfc438caa1",
|
"sha256": "f992dcd6dd7993690d789e2a04b92d4e4be96b708a1a21920c5e8038f2d814c8",
|
||||||
"size": 72992
|
"size": 48962
|
||||||
},
|
},
|
||||||
"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": "2a991cbf4bf38bfb3d1dda876c0c86778e5dbbe75d6eb268a068e3f200ce0c13",
|
"sha256": "5e0e70319c13c39674b3524e30d6ade5c4188ad4f52f331c9bee83193580d0cd",
|
||||||
"size": 40229
|
"size": 40549
|
||||||
},
|
},
|
||||||
"th_10_shell.js": {
|
"th_10_shell.js": {
|
||||||
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
|
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
|
||||||
@@ -54,19 +54,23 @@
|
|||||||
"size": 21198
|
"size": 21198
|
||||||
},
|
},
|
||||||
"th_14_panels.js": {
|
"th_14_panels.js": {
|
||||||
"sha256": "185d7ed9d85ffa7e31b70abbd425c0d79d6a435a4c5cb6dc7610d942fdc0fc87",
|
"sha256": "611c00c8362c346ff0a674aa29cd98e6240cbbe36a2d6c171e0188996bca22a5",
|
||||||
"size": 262019
|
"size": 213491
|
||||||
},
|
},
|
||||||
"th_15_extra.js": {
|
"th_15_extra.js": {
|
||||||
"sha256": "1aa3bbe9c99e047cfaf663f17501d87bba1b5810a8002e66bca1f6ea605371a4",
|
"sha256": "1aa3bbe9c99e047cfaf663f17501d87bba1b5810a8002e66bca1f6ea605371a4",
|
||||||
"size": 99378
|
"size": 99378
|
||||||
},
|
},
|
||||||
|
"th_15_picker.js": {
|
||||||
|
"sha256": "9d59acbbee0db6058e2eba2db310f75b581cbd84d1b8fdaa48f13e244f1cd4f8",
|
||||||
|
"size": 48989
|
||||||
|
},
|
||||||
"th_16_entry.js": {
|
"th_16_entry.js": {
|
||||||
"sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1",
|
"sha256": "9b42884503be328c19a8f95e9e9ff986b1938586909bcf515971f3ff881133be",
|
||||||
"size": 12799
|
"size": 13423
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keyId": "toolhub-targets-2026-rsa3072",
|
"keyId": "toolhub-targets-2026-rsa3072",
|
||||||
"schema": 2,
|
"schema": 2,
|
||||||
"version": 20260518211239
|
"version": 20260521154208
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
PrkoHVO63rKwu62gMoRZjInrjs7YRo6ShBWdms6/U+qDOdLH6uFzIZI8TqLb9AIydoxjoT96r4FuPlL+oOO+FhsjEhlN/mNqUWN3EGvrZn/EyP6P/k4RTHjKSeDKWDzRpbHcKjVJ8FdaMCcXctdPEqiI1B8MLYRZRBwJhMcyLIhh1V0GihTfEvAU6xr+oic7WpO9aMqg4S5Cvi7i9K3mW3QkahYtu1deyB4kE1oGyiYDx60FG2iSOUOykKyt3+Hx9xhU0DB1ZE6T9/9SK8+Ba5kHAALqeUeQGX+49wGvund76bNHmdOfzCSfnKa2enMValNQKNH/hKPFJheh7FO84u+tRXfsBz9SlPVPEsVDaNXgEIJCp+BwU9oYPe7i0SGfmOkBse7agzcLktLUydN8C+xK9xqI4l2vWnOkk5Z/Fj0jVC5+q8WrLhuCLee70RQnHCTQHxh4kDeAvWHjFslCHqSj7TAu7r9IVOvqSqGLFcwiWgoM79XyFf8l0TaXTfwz
|
GfMow5N7dSXGnk4mFX5ENNdLfimG1M2lzV6r3Eur0YpHibkk0p8/Ghz8HnwN5a4sAs7caGEm0msdtbLV4UphuVJoYNUsH2qML7umAnUTyvbgZ0PqV5h0Svw7XXpzYoH9JiHM8bthzKxNqBtftKRFiAIC76Z987WmU5zfflAONWD/LPQixbGndB0CqbubxoATkBzERDb1u5YmqajkfnqPJFuuzhVamfqrTA4UZUbAuwiL48QsABEuiUZdpDa/HVtIEJ+1wO9/OtAhLGOscoNlUJz1OCffSowJ32OUMpwJJHKBmOmVY9S2x+WHOHKE7psuB8DLoVSslsLKlhL76pdblTyMJL/g76GpEx7y/CIoUs0x7ayBsytwl6yCYineyd37PYpNZql+PJ0l9pcDr2gkI54ltxam5z3cWCKAIICfm//Y4R4nEk/GbLI2oXKHoHMskSRedYnIrqH64FUHj6KzcAn+S+uXw91OCaXPM4in0D2oc1C3SKUHtSHM27YQYC6a
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ MODULES = [
|
|||||||
"th_05_persistence.js", "th_06_icon_parser.js", "th_07_shortcut.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_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_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js",
|
||||||
"th_14_panels.js", "th_15_extra.js", "th_16_entry.js",
|
"th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user