28 Commits

Author SHA1 Message Date
7015725
cd3452af45 fix: disable color picker drag translation 2026-05-23 06:28:57 +08:00
7015725
39e62ddaa0 fix: reduce color picker swipe ghosting 2026-05-23 06:22:59 +08:00
7015725
9de9983b50 fix: close color picker on system navigation 2026-05-23 06:19:16 +08:00
7015725
0cc6439619 Add edge swipe dismiss for popup overlays 2026-05-23 06:08:24 +08:00
7015725
095f7ccb39 Pin color picker actions in popup footer 2026-05-23 06:00:44 +08:00
7015725
757a60d15d Align ToolApp back preview shell layout 2026-05-23 05:36:15 +08:00
7015725
d347bb190c Reduce ToolApp back visual displacement 2026-05-23 05:27:10 +08:00
7015725
27dd07da49 Keep back preview during ToolApp pop 2026-05-23 05:20:35 +08:00
7015725
a327ba2657 Avoid scroll restore flicker on ToolApp back 2026-05-23 05:16:59 +08:00
7015725
aa4ef421fe Move ToolApp header upward 2026-05-23 05:09:25 +08:00
7015725
00508c9cde Preserve ToolApp scroll position on back 2026-05-23 04:55:18 +08:00
7015725
407cd2200c fix: align ToolApp top with status bar 2026-05-23 03:12:39 +08:00
7015725
1d49609131 fix: stabilize ToolHub logging 2026-05-23 02:42:46 +08:00
7015725
c62328c419 refactor: polish schema editor blueprint UI 2026-05-23 02:12:28 +08:00
7015725
52b13bea19 refactor: split schema editor module 2026-05-23 01:48:19 +08:00
7015725
c8f8f46054 fix: keep color picker compatible with existing entry 2026-05-23 01:29:05 +08:00
7015725
954caa9d9b refactor: split color picker module 2026-05-23 01:15:56 +08:00
7015725
61eeac6646 refactor: split ShortX icon picker module 2026-05-23 01:05:16 +08:00
7015725
0aa7cbfbfd refactor: add icon picker module placeholder 2026-05-23 00:53:25 +08:00
7015725
3f93a42a00 refactor: remove legacy ToolApp back strips 2026-05-22 13:04:51 +08:00
7015725
7bc4a1bb7d fix: disable legacy ToolApp back strips 2026-05-22 12:55:46 +08:00
7015725
a9db5faf1b fix: prefer ToolApp surface back gesture 2026-05-22 12:41:05 +08:00
7015725
736212fd80 fix: stabilize ToolApp back gesture stack 2026-05-22 12:30:50 +08:00
7015725
83249ab534 Fix ToolApp surface swipe back blocking 2026-05-22 09:16:25 +08:00
7015725
8fd7a560d8 Add ToolApp surface back gesture mode 2026-05-22 08:56:24 +08:00
Hermes
479a23592c Improve ToolApp blank-edge back gestures 2026-05-22 08:38:40 +08:00
Hermes
adc93338cf Improve ToolApp edge back gesture sensitivity 2026-05-22 08:27:23 +08:00
Hermes
4800536de8 Improve ToolApp edge back responsiveness 2026-05-22 08:01:11 +08:00
16 changed files with 2206 additions and 1241 deletions

View File

@@ -33,16 +33,31 @@ function getTrustedShaPath(relPath) { return getCodeDirPath() + ".trusted_sha_"
function getTrustedVersionPath() { return getCodeDirPath() + ".trusted_manifest_version"; } function getTrustedVersionPath() { return getCodeDirPath() + ".trusted_manifest_version"; }
function writeLog(msg) { function writeLog(msg) {
var writer = null;
try { try {
var f = new java.io.File(getLogPath()); var f = new java.io.File(getLogPath());
var dir = f.getParentFile(); var dir = f.getParentFile();
if (dir && !dir.exists()) dir.mkdirs(); if (dir && !dir.exists()) dir.mkdirs();
try {
var maxBytes = 512 * 1024;
if (f.exists() && f.length() > maxBytes) {
var bak = new java.io.File(String(f.getAbsolutePath()) + ".bak");
try { if (bak.exists()) bak.delete(); } catch (eBak0) {}
var moved = false;
try { moved = f.renameTo(bak); } catch (eMv) { moved = false; }
if (!moved) {
try { f.delete(); } catch (eDel) {}
}
}
} catch (eTrim) {}
var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
var ts = sdf.format(new java.util.Date()); var ts = sdf.format(new java.util.Date());
var writer = new java.io.FileWriter(f, true); writer = new java.io.FileWriter(f, true);
writer.write("[" + ts + "] " + String(msg) + "\n"); writer.write("[" + ts + "] " + String(msg) + "\n");
writer.close(); } catch (e) {
} catch (e) {} } finally {
try { if (writer) writer.close(); } catch (eClose) {}
}
} }
function runShell(cmdArr) { function runShell(cmdArr) {
@@ -341,7 +356,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_14_color_picker.js", "th_14_icon_picker.js", "th_14_schema_editor.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 };

View File

@@ -1 +1 @@
458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js 01d93690bdfc402ad59d6f51db45e3e057a3ebcbbf146a5667ecc5486e2d3ed6 ToolHub.js

View File

@@ -100,11 +100,11 @@ var ConfigValidator = {
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: { type: "int", min: 8, max: 80, default: 28 }, 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 }, TOOLAPP_BACK_GESTURE_MODE: { type: "enum", values: ["edge", "surface", "off"], default: "surface" },
ENABLE_TOOLAPP_INNER_BACK_STRIPS: { type: "bool", default: false }, TOOLAPP_BACK_EDGE_WIDTH_DP: { type: "int", min: 1, max: 120, default: 72 },
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: { type: "bool", default: false }, TOOLAPP_BACK_COMMIT_DISTANCE_DP: { type: "int", min: 1, max: 480, default: 36 },
TOOLAPP_BACK_COMMIT_DISTANCE_DP: { type: "int", min: 1, max: 480, default: 72 }, TOOLAPP_BACK_SURFACE_SLOP_DP: { type: "int", min: 8, max: 96, default: 24 },
TOOLAPP_BACK_PROGRESS_DISTANCE_DP: { type: "int", min: 1, max: 720, default: 180 }, TOOLAPP_BACK_PROGRESS_DISTANCE_DP: { type: "int", min: 1, max: 720, default: 96 },
// 功能开关 // 功能开关
ENABLE_SNAP_TO_EDGE: { type: "bool", default: true }, ENABLE_SNAP_TO_EDGE: { type: "bool", default: true },
@@ -736,11 +736,11 @@ var ConfigManager = {
LONG_PRESS_MS: 520, LONG_PRESS_MS: 520,
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: 28, LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: 28,
CLICK_SLOP_DP: 6, CLICK_SLOP_DP: 6,
TOOLAPP_BACK_EDGE_WIDTH_DP: 24, TOOLAPP_BACK_GESTURE_MODE: "surface",
ENABLE_TOOLAPP_INNER_BACK_STRIPS: false, TOOLAPP_BACK_EDGE_WIDTH_DP: 72,
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: false, TOOLAPP_BACK_COMMIT_DISTANCE_DP: 36,
TOOLAPP_BACK_COMMIT_DISTANCE_DP: 72, TOOLAPP_BACK_SURFACE_SLOP_DP: 24,
TOOLAPP_BACK_PROGRESS_DISTANCE_DP: 180, TOOLAPP_BACK_PROGRESS_DISTANCE_DP: 96,
ENABLE_BOUNCE: true, ENABLE_BOUNCE: true,
BOUNCE_TIMES: 2, BOUNCE_TIMES: 2,
BOUNCE_MAX_SCALE: 0.88, BOUNCE_MAX_SCALE: 0.88,
@@ -848,12 +848,16 @@ var ConfigManager = {
{ key: "BOUNCE_STEP_MS", name: "回弹步进时长(ms)", type: "int", min: 20, max: 500, step: 10 }, { key: "BOUNCE_STEP_MS", name: "回弹步进时长(ms)", type: "int", min: 20, max: 500, step: 10 },
{ key: "BOUNCE_DECAY", name: "回弹衰减(0~1)", type: "float", min: 0.30, max: 0.95, step: 0.01 }, { key: "BOUNCE_DECAY", name: "回弹衰减(0~1)", type: "float", min: 0.30, max: 0.95, step: 0.01 },
{ 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: "TOOLAPP_BACK_GESTURE_MODE", name: "设置页滑动返回模式", type: "single_choice", options: [
{ key: "ENABLE_TOOLAPP_INNER_BACK_STRIPS", name: "启用旧版页面覆盖热区(不推荐)", type: "bool" }, { label: "全表面横滑", value: "surface" },
{ key: "ENABLE_TOOLAPP_SCREEN_BACK_STRIPS", name: "启用旧版屏幕覆盖热区(已禁用)", type: "bool" }, { label: "仅面板内部左右边缘", value: "edge" },
{ label: "关闭", value: "off" }
]},
{ key: "TOOLAPP_BACK_EDGE_WIDTH_DP", name: "内部边缘起手宽度", type: "int", min: 1, max: 120, step: 1 },
{ key: "TOOLAPP_BACK_COMMIT_DISTANCE_DP", name: "设置页返回触发距离", type: "int", min: 1, max: 480, step: 1 }, { key: "TOOLAPP_BACK_COMMIT_DISTANCE_DP", name: "设置页返回触发距离", type: "int", min: 1, max: 480, step: 1 },
{ key: "TOOLAPP_BACK_SURFACE_SLOP_DP", name: "表面横滑起手阈值", type: "int", min: 8, max: 96, step: 1 },
{ key: "TOOLAPP_BACK_PROGRESS_DISTANCE_DP", name: "设置页返回动画距离", type: "int", min: 1, max: 720, 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 },
@@ -892,7 +896,10 @@ 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 || 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) { 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_GESTURE_MODE") < 0 || sStr.indexOf("TOOLAPP_BACK_EDGE_WIDTH_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_COMMIT_DISTANCE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_SURFACE_SLOP_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_PROGRESS_DISTANCE_DP") < 0 || sStr.indexOf("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP") < 0) {
needReset = true;
}
if (!needReset && (sStr.indexOf("ENABLE_TOOLAPP_INNER_BACK_STRIPS") >= 0 || sStr.indexOf("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS") >= 0)) {
needReset = true; needReset = true;
} }
@@ -923,10 +930,10 @@ var ConfigManager = {
if (schemaItemDiffers("BALL_ICON_TINT_HEX", ["name", "type"]) || if (schemaItemDiffers("BALL_ICON_TINT_HEX", ["name", "type"]) ||
schemaItemDiffers("BALL_ICON_SIZE_DP", ["name", "type", "min", "max", "step"]) || schemaItemDiffers("BALL_ICON_SIZE_DP", ["name", "type", "min", "max", "step"]) ||
schemaItemDiffers("BALL_BG_COLOR_HEX", ["name", "type"]) || schemaItemDiffers("BALL_BG_COLOR_HEX", ["name", "type"]) ||
schemaItemDiffers("TOOLAPP_BACK_GESTURE_MODE", ["name", "type"]) ||
schemaItemDiffers("TOOLAPP_BACK_EDGE_WIDTH_DP", ["name", "type", "min", "max", "step"]) || 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_COMMIT_DISTANCE_DP", ["name", "type", "min", "max", "step"]) ||
schemaItemDiffers("TOOLAPP_BACK_SURFACE_SLOP_DP", ["name", "type", "min", "max", "step"]) ||
schemaItemDiffers("TOOLAPP_BACK_PROGRESS_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"])) { schemaItemDiffers("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP", ["name", "type", "min", "max", "step"])) {
needReset = true; needReset = true;
@@ -1181,8 +1188,15 @@ function applyRule(rule, kv) {
} }
// =======================【日志:文件写入器(尽力落盘 + 自动清理旧日志)】======================= // =======================【日志:文件写入器(尽力落盘 + 自动清理旧日志)】=======================
// =======================【日志:文件写入器(全局统一目录 + 分级)】======================= function sanitizeLogMessage(msg) {
// 优化后的日志系统(带缓冲,减少文件 IO try {
var s = String(msg == null ? "" : msg);
s = s.replace(/(authorization\s*[:=]\s*bearer\s+)[^\s,;]+/ig, "$1[REDACTED]");
s = s.replace(/((access_)?token|api[_-]?key|password|passwd|secret)(\s*[=:]\s*)[^\s,;]+/ig, "$1$3[REDACTED]");
return s;
} catch (e) { return String(msg == null ? "" : msg); }
}
function ToolHubLogger(procInfo) { function ToolHubLogger(procInfo) {
this.proc = procInfo || {}; this.proc = procInfo || {};
this.dir = PATH_LOG_DIR; this.dir = PATH_LOG_DIR;
@@ -1192,17 +1206,10 @@ function ToolHubLogger(procInfo) {
this.debug = false; this.debug = false;
this.initOk = false; this.initOk = false;
this.lastInitErr = ""; this.lastInitErr = "";
// 新增:日志缓冲
this._buffer = [];
this._bufferSize = 20; // 每 20 条写一次磁盘
this._flushTimer = null;
this._initOnce(); this._initOnce();
} }
ToolHubLogger.prototype._now = function() { return new Date().getTime(); }; ToolHubLogger.prototype._now = function() { return new Date().getTime(); };
ToolHubLogger.prototype._initOnce = function() { ToolHubLogger.prototype._initOnce = function() {
try { try {
if (FileIO.ensureDir(this.dir)) { if (FileIO.ensureDir(this.dir)) {
@@ -1217,110 +1224,22 @@ ToolHubLogger.prototype._initOnce = function() {
this.lastInitErr = String(e); this.lastInitErr = String(e);
} }
}; };
ToolHubLogger.prototype.updateConfig = function(cfg) { ToolHubLogger.prototype.updateConfig = function(cfg) {
if (!cfg) return; if (!cfg) return;
if (typeof cfg.LOG_KEEP_DAYS === "number") this.keepDays = cfg.LOG_KEEP_DAYS; 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_ENABLE !== "undefined") this.enable = !!cfg.LOG_ENABLE;
if (typeof cfg.LOG_DEBUG !== "undefined") this.debug = !!cfg.LOG_DEBUG; 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() { ToolHubLogger.prototype._ymd = function() {
var d = new Date(); var d = new Date();
return "" + d.getFullYear() + return "" + d.getFullYear() +
((d.getMonth() < 9 ? "0" : "") + (d.getMonth() + 1)) + ((d.getMonth() < 9 ? "0" : "") + (d.getMonth() + 1)) +
((d.getDate() < 10 ? "0" : "") + d.getDate()); ((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() + ".log";
return this.dir + "/" + name; return this.dir + "/" + name;
}; };
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) { ToolHubLogger.prototype._line = function(level, msg) {
var ts = this._now(); var ts = this._now();
var d = new Date(ts); var d = new Date(ts);
@@ -1332,7 +1251,7 @@ ToolHubLogger.prototype._line = function(level, msg) {
proc = " uid=" + String(this.proc.uid) + " pid=" + String(this.proc.pid) + " tid=" + String(this.proc.tid) + proc = " uid=" + String(this.proc.uid) + " pid=" + String(this.proc.pid) + " tid=" + String(this.proc.tid) +
" th=" + String(this.proc.threadName) + " proc=" + String(this.proc.processName); " th=" + String(this.proc.threadName) + " proc=" + String(this.proc.processName);
} catch (e0) {} } catch (e0) {}
return t + " [" + String(level) + "] " + String(msg) + proc + "\n"; return t + " [" + String(level) + "] " + sanitizeLogMessage(msg) + proc + "\n";
}; };
ToolHubLogger.prototype._writeRaw = function(level, msg) { ToolHubLogger.prototype._writeRaw = function(level, msg) {
if (!this.initOk) return false; if (!this.initOk) return false;

View File

@@ -56,7 +56,6 @@ function FloatBallAppWM(logger) {
toolAppBackPreviewView: null, toolAppBackPreviewView: null,
toolAppBackPreviewRoute: null, toolAppBackPreviewRoute: null,
toolAppBackPreviewReady: false, toolAppBackPreviewReady: false,
toolAppScreenBackStrips: [],
toolAppTitleView: null, toolAppTitleView: null,
toolAppBackButton: null, toolAppBackButton: null,
settingsGroupKey: null, settingsGroupKey: null,

View File

@@ -288,7 +288,6 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
try { try {
if (this.L) { if (this.L) {
this.L.enable = !!this.config.LOG_ENABLE; this.L.enable = !!this.config.LOG_ENABLE;
this.L.cfg.LOG_ENABLE = !!this.config.LOG_ENABLE;
this.L.i("apply LOG_ENABLE=" + String(this.config.LOG_ENABLE)); this.L.i("apply LOG_ENABLE=" + String(this.config.LOG_ENABLE));
} }
} catch(eLE) { safeLog(null, 'e', "catch " + String(eLE)); } } catch(eLE) { safeLog(null, 'e', "catch " + String(eLE)); }
@@ -298,7 +297,6 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
try { try {
if (this.L) { if (this.L) {
this.L.debug = !!this.config.LOG_DEBUG; this.L.debug = !!this.config.LOG_DEBUG;
this.L.cfg.LOG_DEBUG = !!this.config.LOG_DEBUG;
this.L.i("apply LOG_DEBUG=" + String(this.config.LOG_DEBUG)); this.L.i("apply LOG_DEBUG=" + String(this.config.LOG_DEBUG));
} }
} catch(eLD) { safeLog(null, 'e', "catch " + String(eLD)); } } catch(eLD) { safeLog(null, 'e', "catch " + String(eLD)); }
@@ -310,7 +308,6 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
this.config.LOG_KEEP_DAYS = n; this.config.LOG_KEEP_DAYS = n;
if (this.L) { if (this.L) {
this.L.keepDays = n; this.L.keepDays = n;
this.L.cfg.LOG_KEEP_DAYS = n;
this.L.i("apply LOG_KEEP_DAYS=" + String(n)); this.L.i("apply LOG_KEEP_DAYS=" + String(n));
this.L.cleanupOldFiles(); this.L.cleanupOldFiles();
} }
@@ -319,28 +316,7 @@ FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
} }
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 === "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") { if (k === "TOOLAPP_BACK_EDGE_WIDTH_DP") {
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; return;
} }

View File

@@ -128,7 +128,6 @@ 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) {}
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) {
@@ -196,7 +195,6 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
this.state.toolAppBackPreviewView = null; this.state.toolAppBackPreviewView = null;
this.state.toolAppBackPreviewRoute = null; this.state.toolAppBackPreviewRoute = null;
this.state.toolAppBackPreviewReady = false; this.state.toolAppBackPreviewReady = false;
try { if (this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {}
this.state.toolAppTitleView = null; this.state.toolAppTitleView = null;
this.state.toolAppBackButton = null; this.state.toolAppBackButton = null;
} }
@@ -608,7 +606,6 @@ 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();
@@ -995,10 +992,6 @@ FloatBallAppWM.prototype.onScreenChangedReflow = function(reason) {
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);
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', safeLog(this.L, 'i',
"screen reflow reason=" + String(reason || "") + "screen reflow reason=" + String(reason || "") +
" old=" + oldW + "x" + oldH + " old=" + oldW + "x" + oldH +

View File

@@ -21,24 +21,23 @@ FloatBallAppWM.prototype.execButtonAction = function(btn, idx) {
} }
if (t === "open_viewer") { if (t === "open_viewer") {
var logPath = (this.L && this.L._filePathForToday) ? this.L._filePathForToday() : ""; function tailLogText(path, maxLen) {
if (!logPath) logPath = PATH_LOG_DIR + "/ShortX_ToolHub_" + (new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date())) + ".log"; var txt = FileIO.readText(path);
if (!txt) return "(日志文件不存在或为空: " + path + ")";
var content = FileIO.readText(logPath); txt = String(txt);
if (!content) content = "(日志文件不存在或为空: " + logPath + ")"; if (txt.length > maxLen) txt = "[...前略...]\n" + txt.substring(txt.length - maxLen);
if (content.length > 30000) {
content = "[...前略...]\n" + content.substring(content.length - 30000);
}
// 简单的按行倒序,方便查看最新日志
try { try {
var lines = content.split("\n"); var lines = txt.split("\n");
if (lines.length > 1) { if (lines.length > 1) txt = lines.reverse().join("\n");
content = lines.reverse().join("\n");
}
} catch(eRev) { safeLog(null, 'e', "catch " + String(eRev)); } } catch(eRev) { safeLog(null, 'e', "catch " + String(eRev)); }
return txt;
}
var runLogPath = (this.L && this.L._filePathForToday) ? this.L._filePathForToday() : "";
if (!runLogPath) runLogPath = PATH_LOG_DIR + "/ShortX_ToolHub_" + (new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date())) + ".log";
var initLogPath = PATH_LOG_DIR + "/init.log";
var content = "【启动/更新日志】\n" + tailLogText(initLogPath, 15000) +
"\n\n【运行日志】\n" + tailLogText(runLogPath, 15000);
if (content.length > 32000) content = content.substring(0, 32000) + "\n[...后略...]";
this.showViewerPanel("今日日志 (倒序)", content); this.showViewerPanel("今日日志 (倒序)", content);
return; return;
} }
@@ -132,7 +131,7 @@ return;
if (r && r.ok) return; if (r && r.ok) return;
this.toast("shell 广播桥发送失败"); this.toast("shell 广播桥发送失败");
safeLog(this.L, 'e', "shell all failed cmd_b64=" + cmdB64 + " ret=" + JSON.stringify(r || {})); safeLog(this.L, 'e', "shell all failed cmd_b64_len=" + String(cmdB64 ? cmdB64.length : 0) + " ret=" + JSON.stringify(r || {}));
return; return;
} }

558
code/th_14_color_picker.js Normal file
View File

@@ -0,0 +1,558 @@
// ToolHub - 颜色选择器弹窗模块
// 依赖th_14_panels.js 中的 showPopupOverlay()、th_04_theme.js 主题工具、th_06_icon_parser.js 图标解析。
FloatBallAppWM.prototype.showColorPickerPopup = function(opts) {
var self = this;
var opt = opts || {};
var currentColor = String(opt.currentColor || "");
var currentIconName = String(opt.currentIconName || "");
var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null;
var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null;
var PT = this.getIslandPickerTheme ? this.getIslandPickerTheme() : null;
var isDark = PT ? PT.isDark : this.isDarkTheme();
var C = this.ui.colors;
var T = PT ? PT.T : this.getAnimalIslandTheme();
var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight);
var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight);
function getThemeTintHex() {
try {
if (self.ui.colors && self.ui.colors.accent) {
var c = self.ui.colors.accent;
return "#" + ("00000000" + (c >>> 0).toString(16)).slice(-8);
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
return "#FF4081";
}
function buildArgbHex(alphaByte, rgbHex) {
var a = ("00" + (Math.max(0, Math.min(255, Number(alphaByte || 0))) >>> 0).toString(16)).slice(-2);
var rgb = String(rgbHex || "000000").replace(/^#/, "");
if (rgb.length === 3) rgb = rgb.split("").map(function(c){ return c+c; }).join("");
if (rgb.length > 6) rgb = rgb.slice(-6);
while (rgb.length < 6) rgb = "0" + rgb;
return "#" + a + rgb;
}
function extractTintRgbHex(hex) {
var h = String(hex || "").replace(/^#/, "");
if (h.length >= 8) return h.slice(-6);
if (h.length === 6) return h;
if (h.length === 3) return h.split("").map(function(c){ return c+c; }).join("");
return "000000";
}
function extractTintAlphaByte(hex) {
var h = String(hex || "").replace(/^#/, "");
if (h.length >= 8) return parseInt(h.slice(0, 2), 16);
return 255;
}
function normalizeTintColorValue(val) {
var s = String(val || "").trim();
if (!s) return "";
if (s.charAt(0) === "#") s = s.substring(1);
if (/^[0-9A-Fa-f]{1,8}$/.test(s)) {
while (s.length < 6) s = "0" + s;
if (s.length === 6) s = "FF" + s;
else if (s.length > 8) s = s.substring(0, 8);
return "#" + s.toUpperCase();
}
return "";
}
var commonTintHexValues = [
"#FFFF0000", "#FFFF5722", "#FFFF9800", "#FFFFC107", "#FFFFEB3B",
"#FFCDDC39", "#FF8BC34A", "#FF4CAF50", "#FF009688", "#FF00BCD4",
"#FF03A9F4", "#FF2196F3", "#FF3F51B5", "#FF673AB7", "#FF9C27B0",
"#FFE91E63", "#FF795548", "#FF9E9E9E", "#FF607D8B", "#FF000000", "#FFFFFFFF"
];
// ========== 最近使用颜色 ==========
var RECENT_COLORS_KEY = "color_picker_recent";
var MAX_RECENT_COLORS = 8;
var recentColors = [];
try {
var recentSaved = self.loadPanelState(RECENT_COLORS_KEY);
if (recentSaved && recentSaved.colors && recentSaved.colors.length) {
var rc;
for (rc = 0; rc < recentSaved.colors.length && rc < MAX_RECENT_COLORS; rc++) {
var rn = normalizeTintColorValue(recentSaved.colors[rc], false);
if (rn) recentColors.push(rn);
}
}
} catch(eRecentLoad) { safeLog(null, 'e', "catch " + String(eRecentLoad)); }
function saveRecentColors() {
try {
self.savePanelState(RECENT_COLORS_KEY, { colors: recentColors.slice(0, MAX_RECENT_COLORS) });
} catch(eRecentSave) { safeLog(null, 'e', "catch " + String(eRecentSave)); }
}
function pushRecentColor(hex) {
var normalized = normalizeTintColorValue(hex, false);
if (!normalized) return;
var next = [normalized];
var i;
for (i = 0; i < recentColors.length; i++) {
if (recentColors[i] !== normalized) {
next.push(recentColors[i]);
}
if (next.length >= MAX_RECENT_COLORS) break;
}
recentColors = next;
saveRecentColors();
}
var selectedColor = currentColor;
var isFollowTheme = !currentColor;
var currentBaseRgbHex = extractTintRgbHex(currentColor);
var currentAlphaByte = extractTintAlphaByte(currentColor);
var alphaSeek = null;
var alphaValTv = null;
var updatePreviewFn = function() {};
var updateValueTvFn = function() {};
var refreshRecentGridFn = function() {};
var refreshCommonGridFn = function() {};
var syncRgbSeeksFn = function() {};
// 操作按钮:对齐设置页/按钮管理页的 chip + 主按钮视觉。
function createColorPanelActionButton(label, primary, onClick) {
var b = new android.widget.TextView(context);
b.setText(label);
b.setGravity(android.view.Gravity.CENTER);
b.setSingleLine(true);
b.setTypeface(null, android.graphics.Typeface.BOLD);
try { b.setIncludeFontPadding(false); } catch(eFontPad) {}
if (primary) {
b.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 15);
b.setTextColor(android.graphics.Color.WHITE);
b.setPadding(self.dp(18), 0, self.dp(18), 0);
try { b.setMinHeight(self.dp(52)); } catch(eMinH1) {}
try { b.setBackground(self.ui.createStrokeDrawable(T.primaryDeep, self.withAlpha(T.brown || T.primaryDeep, isDark ? 0.28 : 0.18), self.dp(1), self.dp(26))); } catch(eBg1) {}
try { b.setElevation(self.dp(1)); } catch(eElev) {}
} else {
b.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 15);
b.setTextColor(T.brown || T.sub);
b.setPadding(self.dp(16), 0, self.dp(16), 0);
try { b.setMinHeight(self.dp(52)); } catch(eMinH2) {}
try { b.setBackground(self.ui.createStrokeDrawable(T.card2 || T.card, self.withAlpha(T.stroke || T.brown, isDark ? 0.42 : 0.55), self.dp(1), self.dp(26))); } catch(eBg2) {}
try { b.setElevation(self.dp(1)); } catch(eElev2) {}
}
try { b.setClickable(true); b.setFocusable(true); } catch(eClickable) {}
b.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) {
self.touchActivity();
try { if (onClick) onClick(v); } catch(eBtn) { safeLog(self.L, 'e', "color panel action err=" + String(eBtn)); }
}
}));
return b;
}
var popupResult = self.showPopupOverlay({
title: "换颜色",
onDismiss: onDismiss,
builder: function(content, closePopup) {
// 图标预览区
var previewRow = new android.widget.LinearLayout(context);
previewRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
previewRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
previewRow.setPadding(self.dp(12), self.dp(10), self.dp(12), self.dp(10));
previewRow.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(18)));
var previewIv = new android.widget.ImageView(context);
previewIv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(48), self.dp(48)));
previewIv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER);
previewRow.addView(previewIv);
var previewLabel = new android.widget.TextView(context);
previewLabel.setText("小图标试衣间");
previewLabel.setTextColor(subTextColor);
previewLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
previewLabel.setPadding(self.dp(12), 0, 0, 0);
previewRow.addView(previewLabel);
content.addView(previewRow);
function updatePreview() {
try {
var dr = null;
if (currentIconName) {
try { dr = self.getShortXIconDrawable(currentIconName); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
if (dr) {
if (!isFollowTheme && selectedColor) {
try {
var parsed = android.graphics.Color.parseColor(selectedColor);
dr.setColorFilter(parsed, android.graphics.PorterDuff.Mode.SRC_IN);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
} else {
try { dr.clearColorFilter(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
previewIv.setImageDrawable(dr);
} else {
previewIv.setImageDrawable(null);
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
updatePreviewFn = updatePreview;
updatePreview();
// ========== 最近使用颜色 ==========
var recentTitle = new android.widget.TextView(context);
recentTitle.setText("最近用过的小颜色");
recentTitle.setTextColor(subTextColor);
recentTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
recentTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4));
content.addView(recentTitle);
var recentGrid = new android.widget.GridLayout(context);
recentGrid.setColumnCount(8);
recentGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4));
content.addView(recentGrid);
var recentEmptyTv = new android.widget.TextView(context);
recentEmptyTv.setText("还没有最近颜色");
recentEmptyTv.setTextColor(subTextColor);
recentEmptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
recentEmptyTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8));
recentEmptyTv.setVisibility(android.view.View.GONE);
content.addView(recentEmptyTv);
function refreshRecentGrid() {
try {
recentGrid.removeAllViews();
if (!recentColors.length) {
recentEmptyTv.setVisibility(android.view.View.VISIBLE);
return;
}
recentEmptyTv.setVisibility(android.view.View.GONE);
var ri;
for (ri = 0; ri < recentColors.length && ri < MAX_RECENT_COLORS; ri++) {
(function(hex) {
var cell = new android.widget.FrameLayout(context);
var margin = self.dp(3);
try {
var lp = new android.widget.GridLayout.LayoutParams();
lp.width = self.dp(28);
lp.height = self.dp(28);
lp.setMargins(margin, margin, margin, margin);
cell.setLayoutParams(lp);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
var swatch = new android.view.View(context);
swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT));
try {
var bg = new android.graphics.drawable.GradientDrawable();
bg.setColor(android.graphics.Color.parseColor(hex));
bg.setCornerRadius(self.dp(5));
swatch.setBackground(bg);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
cell.addView(swatch);
if (selectedColor === hex) {
try {
var border = new android.graphics.drawable.GradientDrawable();
border.setColor(android.graphics.Color.TRANSPARENT);
border.setCornerRadius(self.dp(5));
border.setStroke(self.dp(2), T.primaryDeep);
cell.setForeground(border);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
isFollowTheme = false;
selectedColor = hex;
currentBaseRgbHex = extractTintRgbHex(hex);
currentAlphaByte = extractTintAlphaByte(hex);
updatePreview();
updateValueTv();
refreshRecentGrid();
refreshCommonGrid();
syncRgbSeeks();
}
}));
recentGrid.addView(cell);
})(recentColors[ri]);
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
refreshRecentGridFn = refreshRecentGrid;
refreshRecentGrid();
// 21 色常用颜色
var commonTitle = new android.widget.TextView(context);
commonTitle.setText("糖果常用色");
commonTitle.setTextColor(subTextColor);
commonTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
commonTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4));
content.addView(commonTitle);
var commonGrid = new android.widget.GridLayout(context);
commonGrid.setColumnCount(7);
commonGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(8));
var ci;
for (ci = 0; ci < commonTintHexValues.length; ci++) {
(function(hex) {
var cell = new android.widget.FrameLayout(context);
var margin = self.dp(4);
try {
var lp = new android.widget.GridLayout.LayoutParams();
lp.width = self.dp(32);
lp.height = self.dp(32);
lp.setMargins(margin, margin, margin, margin);
cell.setLayoutParams(lp);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
var swatch = new android.view.View(context);
swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT));
try {
var bg = new android.graphics.drawable.GradientDrawable();
bg.setColor(android.graphics.Color.parseColor(hex));
bg.setCornerRadius(self.dp(6));
swatch.setBackground(bg);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
cell.addView(swatch);
if (selectedColor === hex) {
try {
var border = new android.graphics.drawable.GradientDrawable();
border.setColor(android.graphics.Color.TRANSPARENT);
border.setCornerRadius(self.dp(6));
border.setStroke(self.dp(3), T.primaryDeep);
cell.setForeground(border);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
isFollowTheme = false;
selectedColor = hex;
currentBaseRgbHex = extractTintRgbHex(hex);
currentAlphaByte = extractTintAlphaByte(hex);
updatePreview();
updateValueTv();
refreshRecentGrid();
refreshCommonGrid();
syncRgbSeeks();
}
}));
commonGrid.addView(cell);
})(commonTintHexValues[ci]);
}
content.addView(commonGrid);
function refreshCommonGrid() {
try {
var count = commonGrid.getChildCount();
var i;
for (i = 0; i < count; i++) {
var cell = commonGrid.getChildAt(i);
if (!cell) continue;
try { cell.setForeground(null); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
var idx = commonTintHexValues.indexOf(selectedColor);
if (idx >= 0 && idx < count) {
var matchedCell = commonGrid.getChildAt(idx);
if (matchedCell) {
try {
var border = new android.graphics.drawable.GradientDrawable();
border.setColor(android.graphics.Color.TRANSPARENT);
border.setCornerRadius(self.dp(6));
border.setStroke(self.dp(3), T.primaryDeep);
matchedCell.setForeground(border);
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
}
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
refreshCommonGridFn = refreshCommonGrid;
// 颜色值显示
var valueTv = new android.widget.TextView(context);
valueTv.setTextColor(textColor);
valueTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
valueTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4));
content.addView(valueTv);
function updateValueTv() {
valueTv.setText(isFollowTheme ? "当前:跟随岛屿主题" : ("当前:" + (selectedColor || "无")));
}
updateValueTvFn = updateValueTv;
updateValueTv();
// RGB 滑块
var rgbLabels = ["R", "G", "B"];
var rgbSeeks = [];
var rgbValTvs = [];
var ri;
for (ri = 0; ri < 3; ri++) {
(function(idx) {
var row = new android.widget.LinearLayout(context);
row.setOrientation(android.widget.LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
row.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4));
var label = new android.widget.TextView(context);
label.setText(rgbLabels[idx]);
label.setTextColor(textColor);
label.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
label.setMinWidth(self.dp(20));
row.addView(label);
var seek = new android.widget.SeekBar(context);
seek.setMax(255);
var seekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1);
seekLp.setMargins(self.dp(8), 0, self.dp(8), 0);
seek.setLayoutParams(seekLp);
row.addView(seek);
rgbSeeks.push(seek);
var valTv = new android.widget.TextView(context);
valTv.setTextColor(subTextColor);
valTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
valTv.setMinWidth(self.dp(28));
row.addView(valTv);
rgbValTvs.push(valTv);
seek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
onProgressChanged: function(s, progress, fromUser) {
if (!fromUser) return;
valTv.setText(String(progress));
var r = rgbSeeks[0].getProgress();
var g = rgbSeeks[1].getProgress();
var b = rgbSeeks[2].getProgress();
var hex = ("00" + (r >>> 0).toString(16)).slice(-2) + ("00" + (g >>> 0).toString(16)).slice(-2) + ("00" + (b >>> 0).toString(16)).slice(-2);
currentBaseRgbHex = hex;
isFollowTheme = false;
selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex);
updatePreview();
updateValueTv();
refreshRecentGrid();
refreshCommonGrid();
},
onStartTrackingTouch: function() {},
onStopTrackingTouch: function() {}
}));
content.addView(row);
})(ri);
}
function syncRgbSeeks() {
try {
var initR = parseInt(currentBaseRgbHex.slice(0, 2), 16) || 0;
var initG = parseInt(currentBaseRgbHex.slice(2, 4), 16) || 0;
var initB = parseInt(currentBaseRgbHex.slice(4, 6), 16) || 0;
rgbSeeks[0].setProgress(initR);
rgbSeeks[1].setProgress(initG);
rgbSeeks[2].setProgress(initB);
rgbValTvs[0].setText(String(initR));
rgbValTvs[1].setText(String(initG));
rgbValTvs[2].setText(String(initB));
} catch(e) { safeLog(null, 'e', "catch " + String(e)); }
}
syncRgbSeeksFn = syncRgbSeeks;
syncRgbSeeks();
// 透明度滑块
var alphaRow = new android.widget.LinearLayout(context);
alphaRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
alphaRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
alphaRow.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8));
var alphaLabel = new android.widget.TextView(context);
alphaLabel.setText("A");
alphaLabel.setTextColor(textColor);
alphaLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
alphaLabel.setMinWidth(self.dp(20));
alphaRow.addView(alphaLabel);
alphaSeek = new android.widget.SeekBar(context);
alphaSeek.setMax(255);
var alphaSeekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1);
alphaSeekLp.setMargins(self.dp(8), 0, self.dp(8), 0);
alphaSeek.setLayoutParams(alphaSeekLp);
alphaRow.addView(alphaSeek);
alphaValTv = new android.widget.TextView(context);
alphaValTv.setTextColor(subTextColor);
alphaValTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
alphaValTv.setMinWidth(self.dp(28));
alphaRow.addView(alphaValTv);
alphaSeek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
onProgressChanged: function(s, progress, fromUser) {
if (!fromUser) return;
alphaValTv.setText(String(progress));
currentAlphaByte = progress;
isFollowTheme = false;
selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex);
updatePreview();
updateValueTv();
refreshRecentGrid();
refreshCommonGrid();
},
onStartTrackingTouch: function() {},
onStopTrackingTouch: function() {}
}));
alphaSeek.setProgress(currentAlphaByte);
alphaValTv.setText(String(currentAlphaByte));
content.addView(alphaRow);
// 底部操作按钮放到 showPopupOverlay 的固定 footer 中,避免默认首屏看不到。
},
footerBuilder: function(footer, closePopup) {
var actionRow = new android.widget.LinearLayout(context);
actionRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
actionRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
actionRow.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(4));
var btnClear = createColorPanelActionButton("恢复默认", false, function() {
isFollowTheme = true;
selectedColor = "";
currentAlphaByte = 255;
try { updatePreviewFn(); } catch(eUp) {}
try { updateValueTvFn(); } catch(eVal) {}
try { refreshRecentGridFn(); } catch(eRecent) {}
try { refreshCommonGridFn(); } catch(eCommon) {}
try { syncRgbSeeksFn(); } catch(eSync) {}
try { if (alphaSeek) alphaSeek.setProgress(255); } catch(eAlphaSeek) {}
try { if (alphaValTv) alphaValTv.setText("255"); } catch(eAlphaTv) {}
});
var clearLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(52));
clearLp.weight = 1;
clearLp.setMargins(0, 0, self.dp(6), 0);
actionRow.addView(btnClear, clearLp);
var btnOk = createColorPanelActionButton("保存颜色", true, function() {
try {
var finalColor = isFollowTheme ? "" : String(selectedColor || "");
if (!isFollowTheme && selectedColor) pushRecentColor(selectedColor);
if (typeof onSelect === "function") {
try { onSelect(finalColor); } catch(eOnSelect) { safeLog(self.L, 'e', "colorPicker onSelect err=" + String(eOnSelect)); }
}
} catch(e) {
safeLog(self.L, 'e', "colorPicker confirm err=" + String(e));
}
closePopup();
});
var okLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(52));
okLp.weight = 1;
okLp.setMargins(self.dp(6), 0, 0, 0);
actionRow.addView(btnOk, okLp);
footer.addView(actionRow, new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
));
}
});
return popupResult;
};

509
code/th_14_icon_picker.js Normal file
View File

@@ -0,0 +1,509 @@
// @version 1.0.0
// ToolHub - ShortX 图标选择器模块
//
// 阶段 1承载 showShortXIconPickerPopup。
// 目的在新增模块下载、验签、eval 链路已稳定后,迁移 ShortX 图标选择器主体。
// 注意:加载顺序必须位于 th_14_panels.js 之后、th_15_extra.js 之前。
FloatBallAppWM.prototype.showShortXIconPickerPopup = function(opts) {
var self = this;
var opt = opts || {};
var currentName = String(opt.currentName || "");
var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null;
var onDismissCb = (typeof opt.onDismiss === "function") ? opt.onDismiss : null;
var catalog = [];
try { catalog = self.getShortXIconCatalog() || []; } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
if (!catalog.length) {
try { catalog = self.getShortXIconCatalog(true) || []; } catch(e2) { safeLog(null, 'e', "catch " + String(e2)); }
}
if (!catalog.length) {
self.toast("图标库未加载");
return null;
}
var selectedName = currentName;
var popupState = { currentPage: 0, filter: "全部" };
var PT = self.getIslandPickerTheme ? self.getIslandPickerTheme() : null;
var isDark = PT ? PT.isDark : self.isDarkTheme();
var C = self.ui.colors;
var T = PT ? PT.T : self.getAnimalIslandTheme();
var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight);
var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight);
var cardColor = PT ? PT.card2 : (isDark ? C.cardDark : C.cardLight);
var wm = self.state.wm;
var filterTags = ["全部", "常用", "最近", "收藏", "线框", "实心"];
var filterViews = [];
var FAVORITE_ICONS_KEY = "shortx_icon_favorites";
var favoriteIcons = [];
var favoriteMap = {};
function rebuildFavoriteMap() {
favoriteMap = {};
for (var fi = 0; fi < favoriteIcons.length; fi++) {
var fn = String(favoriteIcons[fi] || "");
if (fn) favoriteMap[fn] = true;
}
}
function loadFavoriteIcons() {
favoriteIcons = [];
try {
var saved = self.loadPanelState ? self.loadPanelState(FAVORITE_ICONS_KEY) : null;
var arr = saved && saved.icons ? saved.icons : [];
for (var li = 0; li < arr.length && favoriteIcons.length < 300; li++) {
var name = String(arr[li] || "");
if (name && !favoriteMap[name]) {
favoriteIcons.push(name);
favoriteMap[name] = true;
}
}
} catch(eFavLoad) { safeLog(null, 'e', "catch " + String(eFavLoad)); }
rebuildFavoriteMap();
}
function saveFavoriteIcons() {
try {
if (self.savePanelState) self.savePanelState(FAVORITE_ICONS_KEY, { icons: favoriteIcons.slice(0, 300) });
} catch(eFavSave) { safeLog(null, 'e', "catch " + String(eFavSave)); }
}
function isFavoriteIcon(name) {
return !!favoriteMap[String(name || "")];
}
function toggleFavoriteIcon(name) {
name = String(name || "");
if (!name) return false;
var next = [];
var existed = false;
for (var ti = 0; ti < favoriteIcons.length; ti++) {
var oldName = String(favoriteIcons[ti] || "");
if (!oldName) continue;
if (oldName === name) { existed = true; continue; }
next.push(oldName);
}
if (!existed) next.unshift(name);
favoriteIcons = next.slice(0, 300);
rebuildFavoriteMap();
saveFavoriteIcons();
return !existed;
}
loadFavoriteIcons();
function matchesFilter(entry, f) {
if (!entry) return false;
if (!f || f === "全部") return true;
var n = String(entry.shortName || entry.name || "").toLowerCase();
if (f === "常用") return n.indexOf("home") >= 0 || n.indexOf("share") >= 0 || n.indexOf("search") >= 0 || n.indexOf("settings") >= 0 || n.indexOf("add") >= 0 || n.indexOf("back") >= 0 || n.indexOf("close") >= 0;
if (f === "最近") return selectedName && String(entry.name) === String(selectedName);
if (f === "收藏") return isFavoriteIcon(entry.name);
if (f === "线框") return n.indexOf("outline") >= 0 || n.indexOf("line") >= 0 || n.indexOf("stroke") >= 0 || n.indexOf("border") >= 0;
if (f === "实心") return n.indexOf("fill") >= 0 || n.indexOf("solid") >= 0 || n.indexOf("round") >= 0;
return true;
}
function filterCatalog(q) {
var qLower = String(q || "").toLowerCase();
var out = [];
for (var i = 0; i < catalog.length; i++) {
var entry = catalog[i];
if (!entry) continue;
if (!matchesFilter(entry, popupState.filter)) continue;
if (qLower) {
var n = String(entry.shortName || entry.name).toLowerCase();
if (n.indexOf(qLower) < 0) continue;
}
out.push(entry);
}
return out;
}
var dm = context.getResources().getDisplayMetrics();
var sw = dm.widthPixels;
var sh = dm.heightPixels;
var panelWidth = Math.round(sw * 0.92);
var panelHeight = Math.round(sh * 0.90);
try {
if (self.calculateToolAppLayout) {
var toolLayout = self.calculateToolAppLayout(null);
if (toolLayout && toolLayout.width > 0) panelWidth = toolLayout.width;
if (toolLayout && toolLayout.height > 0) panelHeight = toolLayout.height;
}
} catch(eLayout) { safeLog(null, 'e', "catch " + String(eLayout)); }
if (panelWidth > self.dp(560)) panelWidth = self.dp(560);
if (panelWidth < self.dp(320)) panelWidth = Math.min(sw - self.dp(16), self.dp(320));
if (panelHeight > sh - self.dp(24)) panelHeight = sh - self.dp(24);
if (panelHeight < self.dp(460)) panelHeight = Math.min(sh - self.dp(16), self.dp(460));
var padH = self.dp(14);
var padV = self.dp(12);
var gap = self.dp(8);
var colCount = 5;
var availW = panelWidth - padH * 2 - self.dp(10) * 2;
var cellW = Math.floor((availW - gap * (colCount - 1)) / colCount);
if (cellW < self.dp(46)) cellW = self.dp(46);
var iconSize = Math.max(self.dp(23), Math.min(self.dp(30), Math.floor(cellW * 0.46)));
var cellH = self.dp(70);
var headerH = self.dp(176);
var bottomH = self.dp(58);
var maxGridH = Math.max(self.dp(250), panelHeight - headerH - bottomH);
var rowCount = Math.max(3, Math.floor(maxGridH / (cellH + gap)));
var pageSize = colCount * rowCount;
var rootOverlay = new android.widget.FrameLayout(context);
try { rootOverlay.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, isDark ? 0.58 : 0.42)); }
catch(eOverlayBg) { rootOverlay.setBackgroundColor(0x33000000); }
rootOverlay.setClickable(true);
var card = new android.widget.LinearLayout(context);
card.setOrientation(android.widget.LinearLayout.VERTICAL);
card.setPadding(padH, padV, padH, padV);
card.setBackground(self.ui.createStrokeDrawable(T.card, self.withAlpha(T.primaryDeep, isDark ? 0.28 : 0.22), self.dp(1), self.dp(24)));
try { card.setElevation(self.dp(10)); } catch(eCardElev) { safeLog(null, 'e', "catch " + String(eCardElev)); }
var cardLp = new android.widget.FrameLayout.LayoutParams(panelWidth, panelHeight);
cardLp.gravity = android.view.Gravity.CENTER;
card.setLayoutParams(cardLp);
rootOverlay.addView(card);
var overlayLp = 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_LAYOUT_NO_LIMITS,
android.graphics.PixelFormat.TRANSLUCENT
);
overlayLp.softInputMode = android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
| android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
try { wm.addView(rootOverlay, overlayLp); } catch(eAdd) {
safeLog(self.L, 'e', "icon picker addView fail: " + String(eAdd));
return null;
}
var isDismissed = false;
function dismiss() {
if (isDismissed) return;
isDismissed = true;
try { wm.removeView(rootOverlay); } catch(e) { safeLog(null, 'e', "catch " + String(e)); }
if (typeof onDismissCb === "function") {
try { onDismissCb(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); }
}
}
rootOverlay.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { dismiss(); } }));
card.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { } }));
var header = new android.widget.LinearLayout(context);
header.setOrientation(android.widget.LinearLayout.HORIZONTAL);
header.setGravity(android.view.Gravity.CENTER_VERTICAL);
var titleBox = new android.widget.LinearLayout(context);
titleBox.setOrientation(android.widget.LinearLayout.VERTICAL);
titleBox.setGravity(android.view.Gravity.CENTER_VERTICAL);
titleBox.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
var titleTv = new android.widget.TextView(context);
titleTv.setText("岛上图标库");
titleTv.setTextColor(textColor);
titleTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 17);
titleTv.setTypeface(null, android.graphics.Typeface.BOLD);
titleBox.addView(titleTv);
var countTv = new android.widget.TextView(context);
countTv.setText("共 " + catalog.length + " 个图标");
countTv.setTextColor(subTextColor);
countTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
titleBox.addView(countTv);
header.addView(titleBox);
var closeBtn = self.ui.createFlatButton(self, "✕", T.primaryDeep, function() { dismiss(); });
closeBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18);
closeBtn.setTypeface(null, android.graphics.Typeface.BOLD);
closeBtn.setPadding(self.dp(8), 0, self.dp(8), 0);
try { closeBtn.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), self.dp(1), self.dp(18))); } catch(eCloseBg) {}
header.addView(closeBtn, new android.widget.LinearLayout.LayoutParams(self.dp(42), self.dp(38)));
card.addView(header);
var searchEt = new android.widget.EditText(context);
searchEt.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
searchEt.setTextColor(textColor);
try { searchEt.setHintTextColor(subTextColor); } catch(eHint) { safeLog(null, 'e', "catch " + String(eHint)); }
searchEt.setHint("寻找岛上图标,如 share / home");
searchEt.setSingleLine(true);
searchEt.setFocusable(true);
searchEt.setFocusableInTouchMode(true);
searchEt.setPadding(self.dp(14), self.dp(10), self.dp(14), self.dp(10));
searchEt.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(20)));
var searchLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(46));
searchLp.setMargins(0, self.dp(10), 0, self.dp(8));
card.addView(searchEt, searchLp);
searchEt.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) {
self.touchActivity();
try {
v.requestFocus();
var imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
if (imm) imm.showSoftInput(v, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
} catch(eIme) { safeLog(null, 'e', "catch " + String(eIme)); }
}
}));
var filterScroll = new android.widget.HorizontalScrollView(context);
filterScroll.setHorizontalScrollBarEnabled(false);
var filterRow = new android.widget.LinearLayout(context);
filterRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
filterRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
filterRow.setPadding(0, 0, 0, 0);
filterScroll.addView(filterRow);
var filterLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(36));
filterLp.setMargins(0, 0, 0, self.dp(4));
card.addView(filterScroll, filterLp);
function refreshFilterTags() {
try {
for (var i = 0; i < filterViews.length; i++) {
var item = filterViews[i];
if (!item || !item.view) continue;
var active = item.name === popupState.filter;
item.view.setTextColor(active ? T.onPrimary : T.primaryDeep);
item.view.setTypeface(null, active ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL);
item.view.setBackground(self.ui.createStrokeDrawable(active ? T.primary : T.primarySoft, self.withAlpha(T.primaryDeep, active ? 0.36 : 0.18), self.dp(1), self.dp(16)));
}
} catch(eTags) { safeLog(null, 'e', "catch " + String(eTags)); }
}
for (var ft = 0; ft < filterTags.length; ft++) {
(function(tag) {
var chip = new android.widget.TextView(context);
chip.setText(tag);
chip.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
chip.setGravity(android.view.Gravity.CENTER);
chip.setPadding(self.dp(12), 0, self.dp(12), 0);
chip.setSingleLine(true);
chip.setClickable(true);
chip.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
popupState.filter = tag;
popupState.currentPage = 0;
refreshFilterTags();
renderGrid();
}
}));
var chipLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, self.dp(30));
chipLp.setMargins(0, 0, self.dp(7), 0);
filterRow.addView(chip, chipLp);
filterViews.push({ name: tag, view: chip });
})(filterTags[ft]);
}
refreshFilterTags();
var pageBar = new android.widget.LinearLayout(context);
pageBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
pageBar.setGravity(android.view.Gravity.CENTER_VERTICAL);
pageBar.setPadding(self.dp(2), 0, self.dp(2), self.dp(4));
var pageBarLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(34));
card.addView(pageBar, pageBarLp);
var btnPrev = self.ui.createFlatButton(self, "上一页", self.withAlpha(T.primaryDeep, 0.72), function() {
if (popupState.currentPage > 0) { popupState.currentPage--; renderGrid(); }
});
pageBar.addView(btnPrev, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30)));
var pageInfo = new android.widget.TextView(context);
pageInfo.setTextColor(self.withAlpha(textColor, 0.76));
pageInfo.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
pageInfo.setGravity(android.view.Gravity.CENTER);
pageInfo.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
pageBar.addView(pageInfo);
var btnNext = self.ui.createFlatButton(self, "下一页", self.withAlpha(T.primaryDeep, 0.72), function() {
popupState.currentPage++;
renderGrid();
});
pageBar.addView(btnNext, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30)));
var gridScroll = new android.widget.ScrollView(context);
gridScroll.setVerticalScrollBarEnabled(false);
gridScroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER);
gridScroll.setLayoutParams(new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
card.addView(gridScroll);
var grid = new android.widget.GridLayout(context);
grid.setColumnCount(colCount);
grid.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(8));
gridScroll.addView(grid);
var selectRow = new android.widget.LinearLayout(context);
selectRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
selectRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
selectRow.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8));
selectRow.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.22 : 0.16), self.dp(1), self.dp(22)));
var selectRowLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(58));
selectRowLp.setMargins(0, self.dp(6), 0, 0);
card.addView(selectRow, selectRowLp);
var selectNameTv = new android.widget.TextView(context);
selectNameTv.setTextColor(textColor);
selectNameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
selectNameTv.setSingleLine(true);
try { selectNameTv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eEll) {}
selectNameTv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
selectRow.addView(selectNameTv);
var selectConfirm = self.ui.createSolidButton(self, "带回小岛", T.primary, T.onPrimary, function() {
self.touchActivity();
try {
if (typeof onSelect === "function") onSelect(selectedName);
} catch(eSelect) {
safeLog(self.L, 'e', "icon onSelect err=" + String(eSelect));
}
dismiss();
});
var confirmLp = new android.widget.LinearLayout.LayoutParams(self.dp(104), self.dp(42));
confirmLp.setMargins(self.dp(10), 0, 0, 0);
selectRow.addView(selectConfirm, confirmLp);
function updateSelectedLabel() {
try {
selectNameTv.setText(selectedName ? ("已选:" + String(selectedName)) : "还没选图标");
} catch(eLabel) { safeLog(null, 'e', "catch " + String(eLabel)); }
}
updateSelectedLabel();
function renderGrid() {
try {
grid.removeAllViews();
var q = String(searchEt.getText() || "");
var matched = filterCatalog(q);
var totalPages = Math.max(1, Math.ceil(matched.length / pageSize));
if (popupState.currentPage >= totalPages) popupState.currentPage = totalPages - 1;
if (popupState.currentPage < 0) popupState.currentPage = 0;
var start = popupState.currentPage * pageSize;
var pageItems = matched.slice(start, start + pageSize);
pageInfo.setText("第 " + (popupState.currentPage + 1) + " / " + totalPages + " 页");
btnPrev.setEnabled(popupState.currentPage > 0);
btnNext.setEnabled(popupState.currentPage < totalPages - 1);
if (pageItems.length === 0) {
var emptyTv = new android.widget.TextView(context);
emptyTv.setText(popupState.filter === "收藏" ? "收藏夹还空着,点图标左上角 ☆ 收藏" : "没有找到这枚小图标");
emptyTv.setTextColor(subTextColor);
emptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
emptyTv.setGravity(android.view.Gravity.CENTER);
emptyTv.setPadding(0, self.dp(60), 0, self.dp(60));
grid.addView(emptyTv);
return;
}
grid.setColumnCount(colCount);
for (var idx = 0; idx < pageItems.length; idx++) {
(function(item) {
var frame = new android.widget.FrameLayout(context);
frame.setClickable(true);
var isSelected = selectedName === item.name;
var frameBg = isSelected ? T.primarySoft : self.withAlpha(cardColor, 0.96);
var frameStroke = isSelected ? self.withAlpha(T.primaryDeep, isDark ? 0.50 : 0.42) : self.withAlpha(T.primaryDeep, isDark ? 0.18 : 0.12);
frame.setBackground(self.ui.createStrokeDrawable(frameBg, frameStroke, isSelected ? self.dp(2) : self.dp(1), self.dp(15)));
var cell = new android.widget.LinearLayout(context);
cell.setOrientation(android.widget.LinearLayout.VERTICAL);
cell.setGravity(android.view.Gravity.CENTER);
cell.setPadding(self.dp(4), self.dp(6), self.dp(4), self.dp(5));
frame.addView(cell, new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT));
var iv = new android.widget.ImageView(context);
iv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(iconSize, iconSize));
iv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER);
try {
var dr = self.getShortXIconDrawable(item.name);
if (dr) { iv.setImageDrawable(dr); }
} catch(eIcon) { safeLog(null, 'e', "catch " + String(eIcon)); }
cell.addView(iv);
var tv = new android.widget.TextView(context);
tv.setText(String(item.shortName || item.name));
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 9);
tv.setGravity(android.view.Gravity.CENTER);
tv.setMaxLines(1);
try { tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eTvEll) {}
tv.setPadding(self.dp(2), self.dp(5), self.dp(2), 0);
tv.setTextColor(isSelected ? T.primaryDeep : subTextColor);
cell.addView(tv, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
var favBtn = new android.widget.TextView(context);
favBtn.setText(isFavoriteIcon(item.name) ? "★" : "☆");
favBtn.setTextColor(isFavoriteIcon(item.name) ? T.primaryDeep : self.withAlpha(T.primaryDeep, 0.52));
favBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
favBtn.setGravity(android.view.Gravity.CENTER);
favBtn.setTypeface(null, android.graphics.Typeface.BOLD);
favBtn.setBackground(self.ui.createRoundDrawable(isFavoriteIcon(item.name) ? T.primarySoft : self.withAlpha(T.card, 0.88), self.dp(9)));
favBtn.setClickable(true);
favBtn.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
var added = toggleFavoriteIcon(item.name);
try { self.toast(added ? "已收藏到小岛" : "已取消收藏"); } catch(eFavToast) {}
if (popupState.filter === "收藏") popupState.currentPage = 0;
renderGrid();
}
}));
var favLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18));
favLp.gravity = android.view.Gravity.TOP | android.view.Gravity.LEFT;
favLp.setMargins(self.dp(4), self.dp(4), 0, 0);
frame.addView(favBtn, favLp);
if (isSelected) {
var badge = new android.widget.TextView(context);
badge.setText("✓");
badge.setTextColor(T.onPrimary);
badge.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10);
badge.setGravity(android.view.Gravity.CENTER);
badge.setTypeface(null, android.graphics.Typeface.BOLD);
badge.setBackground(self.ui.createRoundDrawable(T.primary, self.dp(9)));
var badgeLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18));
badgeLp.gravity = android.view.Gravity.TOP | android.view.Gravity.RIGHT;
badgeLp.setMargins(0, self.dp(4), self.dp(4), 0);
frame.addView(badge, badgeLp);
}
frame.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
selectedName = item.name;
updateSelectedLabel();
renderGrid();
}
}));
var cellLp = new android.widget.GridLayout.LayoutParams();
cellLp.width = cellW;
cellLp.height = cellH;
var col = idx % colCount;
var mr = (col === colCount - 1) ? 0 : gap;
cellLp.setMargins(0, 0, mr, gap);
frame.setLayoutParams(cellLp);
grid.addView(frame);
})(pageItems[idx]);
}
} catch(eRender) {
safeLog(self.L, 'e', "renderShortXIconGrid err=" + String(eRender));
}
}
searchEt.addTextChangedListener(new android.text.TextWatcher({
beforeTextChanged: function() {},
onTextChanged: function() {
self.touchActivity();
popupState.currentPage = 0;
renderGrid();
},
afterTextChanged: function() {}
}));
renderGrid();
return { close: dismiss };
};

File diff suppressed because it is too large Load Diff

392
code/th_14_schema_editor.js Normal file
View File

@@ -0,0 +1,392 @@
// ToolHub - 高级蓝图编辑器模块
// 依赖th_14_panels.js 的设置页主题/基础 UIth_05_persistence.js 的 ConfigManager。
// 加载顺序th_14_panels.js 之后th_15_extra.js 之前。
FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() {
var self = this;
if (this.state.editingSchemaIndex === undefined) this.state.editingSchemaIndex = null;
if (!this.state.keepSchemaEditorState || !this.state.tempSchema) {
var current = ConfigManager.loadSchema();
this.state.tempSchema = JSON.parse(JSON.stringify(current));
}
this.state.keepSchemaEditorState = false;
var schema = this.state.tempSchema || [];
var isEditing = (this.state.editingSchemaIndex !== null && this.state.editingSchemaIndex !== undefined);
var isDark = this.isDarkTheme();
var C = this.ui.colors;
var T = this.getAnimalIslandTheme ? this.getAnimalIslandTheme() : null;
try { if (this.applySettingsTheme) this.applySettingsTheme(T, isDark, C, this.state.pendingUserCfg || this.config); } catch(eTheme) {}
var bgColor = T && T.bg ? T.bg : (isDark ? C.bgDark : C.bgLight);
var cardColor = T && T.card ? T.card : (isDark ? C.cardDark : C.cardLight);
var textColor = T && T.text ? T.text : (isDark ? C.textPriDark : C.textPriLight);
var subTextColor = T && T.sub ? T.sub : (isDark ? C.textSecDark : C.textSecLight);
var primaryColor = T && T.primary ? T.primary : C.primary;
var primaryDeep = T && T.primaryDeep ? T.primaryDeep : C.primary;
var primarySoft = T && T.primarySoft ? T.primarySoft : self.withAlpha(primaryColor, isDark ? 0.22 : 0.12);
var strokeColor = T && T.stroke ? T.stroke : (isDark ? C.dividerDark : C.dividerLight);
var inputBgColor = isDark ? C.inputBgDark : C.inputBgLight;
var dangerColor = C.danger || C.error || 0xffd32f2f;
function lpFullWrap() {
return new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
}
function addWithMargins(parent, view, l, t, r, b) {
var lp = lpFullWrap();
lp.setMargins(self.dp(l || 0), self.dp(t || 0), self.dp(r || 0), self.dp(b || 0));
parent.addView(view, lp);
}
function makeText(txt, sp, color, bold) {
var tv = new android.widget.TextView(context);
tv.setText(String(txt || ""));
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sp || 13);
tv.setTextColor(color || textColor);
if (bold) tv.setTypeface(null, android.graphics.Typeface.BOLD);
return tv;
}
function makeChip(txt, color, onClick) {
var tv = new android.widget.TextView(context);
tv.setText(String(txt || ""));
tv.setTextColor(color || primaryDeep);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tv.setGravity(android.view.Gravity.CENTER);
tv.setTypeface(null, android.graphics.Typeface.BOLD);
tv.setMinHeight(self.dp(32));
tv.setPadding(self.dp(10), 0, self.dp(10), 0);
try { tv.setBackground(self.ui.createStrokeDrawable(self.withAlpha(color || primaryColor, isDark ? 0.16 : 0.08), self.withAlpha(color || primaryColor, isDark ? 0.45 : 0.28), self.dp(1), self.dp(16))); } catch(eBg) {}
if (onClick) {
tv.setOnClickListener(new android.view.View.OnClickListener({ onClick: function(v) {
try { self.touchActivity(); } catch(eTouch) {}
try { onClick(v); } catch(eClick) { safeLog(null, 'e', "schema chip click err=" + String(eClick)); }
}}));
}
return tv;
}
function showToast(msg) { try { self.toast(String(msg || "")); } catch(eToast) {} }
function refreshPanel() {
self.state.keepSchemaEditorState = true;
self.showPanelAvoidBall("schema_editor");
}
function clearDraft() {
self.state.schemaEditingDraft = null;
self.state.schemaEditingDraftIndex = null;
}
function normalizeItem(item) {
var it = item || {};
it.type = String(it.type || "bool");
if (it.type !== "section") {
it.key = String(it.key || "").trim();
}
it.name = String(it.name || it.key || "").trim();
if (it.type === "section") {
delete it.key; delete it.min; delete it.max; delete it.step; delete it.action;
} else if (it.type === "bool" || it.type === "text") {
delete it.min; delete it.max; delete it.step; delete it.action;
} else if (it.type === "int" || it.type === "float") {
delete it.action;
if (it.min === "" || isNaN(Number(it.min))) delete it.min; else it.min = Number(it.min);
if (it.max === "" || isNaN(Number(it.max))) delete it.max; else it.max = Number(it.max);
if (it.step === "" || isNaN(Number(it.step))) delete it.step; else it.step = Number(it.step);
} else if (it.type === "action") {
delete it.min; delete it.max; delete it.step;
it.action = String(it.action || "").trim();
}
return it;
}
function validateItem(item) {
if (!item.name) return "请填写显示名字";
if (item.type !== "section" && !item.key) return "请填写配置键";
if (item.type === "action" && !item.action) return "请填写动作 ID";
return "";
}
var panel = this.ui.createStyledPanel(this, 16);
try { panel.setBackground(this.ui.createRoundDrawable(bgColor, this.dp(20))); } catch(ePanelBg) {}
if (!isEditing) {
var topCard = new android.widget.LinearLayout(context);
topCard.setOrientation(android.widget.LinearLayout.VERTICAL);
topCard.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12));
try { topCard.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.32 : 0.42), self.dp(1), self.dp(20))); } catch(eTopBg) {}
var titleRow = new android.widget.LinearLayout(context);
titleRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
titleRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
var titleBox = new android.widget.LinearLayout(context);
titleBox.setOrientation(android.widget.LinearLayout.VERTICAL);
titleBox.addView(makeText("岛屿蓝图", 17, textColor, true));
titleBox.addView(makeText("这里会改变设置页结构,建议只在需要整理入口时使用", 12, subTextColor, false));
titleRow.addView(titleBox, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
titleRow.addView(makeChip("添加", primaryDeep, function() {
self.state.editingSchemaIndex = -1;
clearDraft();
if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor");
else refreshPanel();
}));
topCard.addView(titleRow);
var stat = makeText("共 " + schema.length + " 个蓝图项 · 保存后才会生效", 12, subTextColor, false);
stat.setPadding(0, self.dp(8), 0, 0);
topCard.addView(stat);
addWithMargins(panel, topCard, 0, 0, 0, 8);
var scroll = new android.widget.ScrollView(context);
try { scroll.setVerticalScrollBarEnabled(false); scroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eScroll) {}
var list = new android.widget.LinearLayout(context);
list.setOrientation(android.widget.LinearLayout.VERTICAL);
list.setPadding(0, self.dp(2), 0, self.dp(10));
if (!schema.length) {
var empty = makeText("蓝图列表为空,可点上方“添加”创建新项。", 13, subTextColor, false);
empty.setGravity(android.view.Gravity.CENTER);
empty.setPadding(self.dp(12), self.dp(24), self.dp(12), self.dp(24));
list.addView(empty, lpFullWrap());
}
for (var i = 0; i < schema.length; i++) {
(function(idx) {
var item = schema[idx] || {};
var card = new android.widget.LinearLayout(context);
card.setOrientation(android.widget.LinearLayout.VERTICAL);
card.setPadding(self.dp(13), self.dp(11), self.dp(13), self.dp(10));
try { card.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.28 : 0.36), self.dp(1), self.dp(18))); card.setElevation(self.dp(1)); } catch(eCardBg) {}
var row = new android.widget.LinearLayout(context);
row.setOrientation(android.widget.LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
var badge = makeText(item.type === "section" ? "分组" : String(item.type || "项"), 11, primaryDeep, true);
badge.setGravity(android.view.Gravity.CENTER);
badge.setPadding(self.dp(8), self.dp(3), self.dp(8), self.dp(3));
try { badge.setBackground(self.ui.createStrokeDrawable(primarySoft, self.withAlpha(primaryDeep, 0.25), self.dp(1), self.dp(12))); } catch(eBadge) {}
row.addView(badge);
var info = new android.widget.LinearLayout(context);
info.setOrientation(android.widget.LinearLayout.VERTICAL);
info.setPadding(self.dp(10), 0, 0, 0);
var nameTv = makeText(String(item.name || item.key || "未命名蓝图项"), 14, textColor, true);
info.addView(nameTv);
var sub = item.type === "section" ? "分组标题" : ("配置键:" + String(item.key || "未填写"));
info.addView(makeText(sub, 11, subTextColor, false));
row.addView(info, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
card.addView(row);
var actions = new android.widget.LinearLayout(context);
actions.setOrientation(android.widget.LinearLayout.HORIZONTAL);
actions.setGravity(android.view.Gravity.RIGHT | android.view.Gravity.CENTER_VERTICAL);
actions.setPadding(0, self.dp(9), 0, 0);
if (idx > 0) actions.addView(makeChip("上搬", subTextColor, function() { var tmp = schema[idx]; schema[idx] = schema[idx - 1]; schema[idx - 1] = tmp; refreshPanel(); }));
if (idx < schema.length - 1) actions.addView(makeChip("下搬", subTextColor, function() { var tmp = schema[idx]; schema[idx] = schema[idx + 1]; schema[idx + 1] = tmp; refreshPanel(); }));
actions.addView(makeChip("编辑", primaryDeep, function() {
self.state.editingSchemaIndex = idx;
clearDraft();
if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor");
else refreshPanel();
}));
actions.addView(makeChip("移除", dangerColor, function() { schema.splice(idx, 1); refreshPanel(); }));
card.addView(actions);
addWithMargins(list, card, 2, 4, 2, 6);
})(i);
}
scroll.addView(list);
panel.addView(scroll, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
var bottom = new android.widget.LinearLayout(context);
bottom.setOrientation(android.widget.LinearLayout.HORIZONTAL);
bottom.setGravity(android.view.Gravity.CENTER_VERTICAL);
bottom.setPadding(0, self.dp(8), 0, 0);
var resetBtn = makeChip("恢复默认蓝图", dangerColor, function() {
ConfigManager.resetSchema();
self.state.tempSchema = null;
clearDraft();
showToast("已恢复默认蓝图");
refreshPanel();
});
bottom.addView(resetBtn, new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1));
var saveBtn = self.ui.createSolidButton(self, "保存蓝图", primaryColor, T && T.onPrimary ? T.onPrimary : android.graphics.Color.WHITE, function() {
ConfigManager.saveSchema(schema);
self.state.tempSchema = null;
clearDraft();
showToast("蓝图已保存");
if (self.state.toolAppActive && self.popToolAppPage) {
self.state.editingSchemaIndex = null;
self.popToolAppPage("schema_save_all");
} else {
self.hideAllPanels();
self.showPanelAvoidBall("settings");
}
});
var saveLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1);
saveLp.setMargins(self.dp(8), 0, 0, 0);
bottom.addView(saveBtn, saveLp);
panel.addView(bottom, lpFullWrap());
return panel;
}
var editIdx = this.state.editingSchemaIndex;
if (this.state.schemaEditingDraftIndex !== editIdx || !this.state.schemaEditingDraft) {
var baseItem = (editIdx === -1) ? { type: "bool", name: "", key: "" } : (schema[editIdx] ? JSON.parse(JSON.stringify(schema[editIdx])) : { type: "bool", name: "", key: "" });
this.state.schemaEditingDraft = baseItem;
this.state.schemaEditingDraftIndex = editIdx;
}
var editItem = this.state.schemaEditingDraft;
if (!editItem.type) editItem.type = "bool";
var formCard = new android.widget.LinearLayout(context);
formCard.setOrientation(android.widget.LinearLayout.VERTICAL);
formCard.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12));
try { formCard.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.32 : 0.42), self.dp(1), self.dp(20))); } catch(eFormBg) {}
var editTitle = makeText(editIdx === -1 ? "添加蓝图项" : "整理蓝图项", 17, textColor, true);
formCard.addView(editTitle);
var editHint = makeText("修改后先“暂存”,回到列表再统一保存蓝图。", 12, subTextColor, false);
editHint.setPadding(0, self.dp(4), 0, self.dp(10));
formCard.addView(editHint);
var schemaInlineNotice = makeText("", 12, primaryDeep, false);
schemaInlineNotice.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8));
schemaInlineNotice.setVisibility(android.view.View.GONE);
function updateSchemaInlineNotice(msg, kind) {
try {
var color = String(kind || "info") === "error" ? dangerColor : primaryDeep;
schemaInlineNotice.setText(String(msg || ""));
schemaInlineNotice.setTextColor(color);
schemaInlineNotice.setBackground(self.ui.createStrokeDrawable(self.withAlpha(color, isDark ? 0.18 : 0.08), self.withAlpha(color, isDark ? 0.42 : 0.25), self.dp(1), self.dp(14)));
schemaInlineNotice.setVisibility(android.view.View.VISIBLE);
} catch(eNotice) {}
}
formCard.addView(schemaInlineNotice, lpFullWrap());
var scroll2 = new android.widget.ScrollView(context);
try { scroll2.setVerticalScrollBarEnabled(false); scroll2.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eScroll2) {}
var form = new android.widget.LinearLayout(context);
form.setOrientation(android.widget.LinearLayout.VERTICAL);
scroll2.addView(form);
function createInput(label, key, inputType, hint) {
var box = new android.widget.LinearLayout(context);
box.setOrientation(android.widget.LinearLayout.VERTICAL);
box.setPadding(0, 0, 0, self.dp(10));
box.addView(makeText(label, 12, subTextColor, false));
var et = new android.widget.EditText(context);
et.setText(String(editItem[key] !== undefined && editItem[key] !== null ? editItem[key] : ""));
et.setTextColor(textColor);
et.setHint(String(hint || ""));
et.setHintTextColor(self.withAlpha(subTextColor, 0.55));
et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
et.setSingleLine(true);
et.setBackground(self.ui.createStrokeDrawable(inputBgColor, self.withAlpha(strokeColor, 0.55), self.dp(1), self.dp(12)));
et.setPadding(self.dp(12), self.dp(7), self.dp(12), self.dp(7));
if (inputType) et.setInputType(inputType);
box.addView(et, lpFullWrap());
form.addView(box, lpFullWrap());
return { input: et, getValue: function() { return String(et.getText()); } };
}
var inputName = null;
var inputKey = null;
var inputMin = null;
var inputMax = null;
var inputStep = null;
var inputAction = null;
function syncDraftFromInputs() {
try {
if (inputName) editItem.name = inputName.getValue();
if (inputKey) editItem.key = inputKey.getValue();
if (inputMin) editItem.min = inputMin.getValue();
if (inputMax) editItem.max = inputMax.getValue();
if (inputStep) editItem.step = inputStep.getValue();
if (inputAction) editItem.action = inputAction.getValue();
self.state.schemaEditingDraft = editItem;
self.state.schemaEditingDraftIndex = editIdx;
} catch(eSync) { safeLog(null, 'e', "schema draft sync err=" + String(eSync)); }
}
var typeRow = new android.widget.LinearLayout(context);
typeRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
typeRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
typeRow.setPadding(0, 0, 0, self.dp(10));
typeRow.addView(makeText("项目类型", 12, subTextColor, false), new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
var types = ["section", "bool", "int", "float", "text", "action"];
var typeNames = { section: "分组标题", bool: "开关", int: "整数", float: "小数", text: "文本", action: "动作入口" };
typeRow.addView(makeChip(typeNames[String(editItem.type)] || String(editItem.type), primaryDeep, function() {
syncDraftFromInputs();
var currIdx = types.indexOf(String(editItem.type || "bool"));
if (currIdx < 0) currIdx = 1;
editItem.type = types[(currIdx + 1) % types.length];
self.state.schemaEditingDraft = editItem;
self.state.schemaEditingDraftIndex = editIdx;
refreshPanel();
}));
form.addView(typeRow, lpFullWrap());
inputName = createInput("显示名字", "name", android.text.InputType.TYPE_CLASS_TEXT, "例如:漂浮气球");
if (editItem.type !== "section") {
inputKey = createInput("配置键", "key", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如BALL_SIZE_DP");
}
if (editItem.type === "int" || editItem.type === "float") {
inputMin = createInput("最小值", "min", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED, "可留空");
inputMax = createInput("最大值", "max", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED, "可留空");
inputStep = createInput("步进", "step", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, "可留空");
}
if (editItem.type === "action") {
inputAction = createInput("动作 ID", "action", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如open_btn_mgr");
}
formCard.addView(scroll2, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
panel.addView(formCard, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
var editBottom = new android.widget.LinearLayout(context);
editBottom.setOrientation(android.widget.LinearLayout.HORIZONTAL);
editBottom.setGravity(android.view.Gravity.CENTER_VERTICAL);
editBottom.setPadding(0, self.dp(8), 0, 0);
editBottom.addView(makeChip("不改了", subTextColor, function() {
clearDraft();
self.state.editingSchemaIndex = null;
if (self.state.toolAppActive && self.popToolAppPage) {
self.state.keepSchemaEditorState = true;
self.popToolAppPage("schema_edit_back");
} else refreshPanel();
}), new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1));
var saveDraftBtn = self.ui.createSolidButton(self, "暂存蓝图项", primaryColor, T && T.onPrimary ? T.onPrimary : android.graphics.Color.WHITE, function() {
try {
syncDraftFromInputs();
normalizeItem(editItem);
var err = validateItem(editItem);
if (err) {
updateSchemaInlineNotice(err, "error");
try { scroll2.fullScroll(android.view.View.FOCUS_UP); } catch(eFocusUp) {}
return;
}
if (editIdx === -1) schema.push(JSON.parse(JSON.stringify(editItem)));
else schema[editIdx] = JSON.parse(JSON.stringify(editItem));
clearDraft();
self.state.editingSchemaIndex = null;
if (self.state.toolAppActive && self.popToolAppPage) {
self.state.keepSchemaEditorState = true;
self.popToolAppPage("schema_edit_save");
} else refreshPanel();
} catch(eSave) {
updateSchemaInlineNotice("暂存失败:" + String(eSave), "error");
try { scroll2.fullScroll(android.view.View.FOCUS_UP); } catch(eFocusUp2) {}
}
});
var saveDraftLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1);
saveDraftLp.setMargins(self.dp(8), 0, 0, 0);
editBottom.addView(saveDraftBtn, saveDraftLp);
panel.addView(editBottom, lpFullWrap());
return panel;
};

View File

@@ -545,6 +545,7 @@ FloatBallAppWM.prototype.closeToolApp = function() {
this.state.toolAppActive = false; this.state.toolAppActive = false;
this.state.toolAppRoute = null; this.state.toolAppRoute = null;
this.state.toolAppNavStack = []; this.state.toolAppNavStack = [];
try { this.bumpToolAppStackVersion(); } catch(eStackClose) {}
this.state.settingsGroupKey = null; this.state.settingsGroupKey = null;
this.state.settingsHomeSelectedItemId = null; this.state.settingsHomeSelectedItemId = null;
this.hideViewerPanel(); this.hideViewerPanel();
@@ -554,9 +555,11 @@ FloatBallAppWM.prototype.closeToolApp = function() {
this.state.toolAppBackPreviewView = null; this.state.toolAppBackPreviewView = null;
this.state.toolAppBackPreviewRoute = null; this.state.toolAppBackPreviewRoute = null;
this.state.toolAppBackPreviewReady = false; this.state.toolAppBackPreviewReady = false;
try { if (this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {} this.state.toolAppBackPreviewStackVersion = null;
this.state.toolAppBackPreviewEntryKey = null;
this.state.toolAppTitleView = null; this.state.toolAppTitleView = null;
this.state.toolAppBackButton = null; this.state.toolAppBackButton = null;
this.state.toolAppScrollY = 0;
} catch (e) { safeLog(this.L, 'e', "closeToolApp fail: " + String(e)); } } catch (e) { safeLog(this.L, 'e', "closeToolApp fail: " + String(e)); }
}; };
@@ -571,6 +574,8 @@ FloatBallAppWM.prototype.clearToolAppBackPreview = function(resetCurrent) {
this.state.toolAppBackPreviewView = null; this.state.toolAppBackPreviewView = null;
this.state.toolAppBackPreviewRoute = null; this.state.toolAppBackPreviewRoute = null;
this.state.toolAppBackPreviewReady = false; this.state.toolAppBackPreviewReady = false;
this.state.toolAppBackPreviewStackVersion = null;
this.state.toolAppBackPreviewEntryKey = null;
if (resetCurrent && root) { if (resetCurrent && root) {
try { root.animate().cancel(); } catch (eCancelRoot) {} try { root.animate().cancel(); } catch (eCancelRoot) {}
try { root.setTranslationX(0); root.setAlpha(1); root.setScaleX(1); root.setScaleY(1); } catch (eRoot) {} try { root.setTranslationX(0); root.setAlpha(1); root.setScaleX(1); root.setScaleY(1); } catch (eRoot) {}
@@ -611,9 +616,152 @@ FloatBallAppWM.prototype.hasToolAppBackTarget = function() {
return false; return false;
}; };
FloatBallAppWM.prototype.cloneToolAppSnapshotValue = function(v, depth) {
try {
if (depth === undefined || depth === null) depth = 0;
if (depth > 4) return null;
if (v === null || v === undefined) return v;
var t = typeof v;
if (t === "string" || t === "number" || t === "boolean") return v;
if (v instanceof java.lang.String) return String(v);
if (v instanceof java.lang.Number) return Number(v);
if (v instanceof java.lang.Boolean) return Boolean(v);
var tag = Object.prototype.toString.call(v);
if (tag === "[object Array]") {
var arr = [];
for (var i = 0; i < v.length; i++) arr.push(this.cloneToolAppSnapshotValue(v[i], depth + 1));
return arr;
}
if (t === "object") {
// 只深拷贝普通 JS 对象,避免把 Android View / Java 对象枚举进页面栈快照。
if (tag !== "[object Object]") return String(v);
var out = {};
for (var k in v) {
try {
if (!v.hasOwnProperty || v.hasOwnProperty(k)) out[k] = this.cloneToolAppSnapshotValue(v[k], depth + 1);
} catch(eKey) {}
}
return out;
}
} catch(e) {}
return null;
};
FloatBallAppWM.prototype.getToolAppStackVersion = function() {
try {
var v = Number(this.state.toolAppNavStackVersion || 0);
if (isNaN(v) || v < 0) v = 0;
this.state.toolAppNavStackVersion = v;
return v;
} catch(e) {}
return 0;
};
FloatBallAppWM.prototype.bumpToolAppStackVersion = function() {
try {
var v = this.getToolAppStackVersion ? this.getToolAppStackVersion() : Number(this.state.toolAppNavStackVersion || 0);
v = v + 1;
if (v > 1000000000) v = 1;
this.state.toolAppNavStackVersion = v;
if (!this.state.keepToolAppBackPreviewDuringPop) this.clearToolAppBackPreview(false);
return v;
} catch(e) {}
return 0;
};
FloatBallAppWM.prototype.getToolAppSnapshotKey = function(entry) {
try { return JSON.stringify(entry || {}); } catch(e) {}
return String(entry && entry.route || "");
};
FloatBallAppWM.prototype.findToolAppFirstScrollView = function(root) {
try {
if (!root) return null;
if (root instanceof android.widget.ScrollView) return root;
if (!root.getChildCount) return null;
var count = root.getChildCount();
for (var i = 0; i < count; i++) {
var found = this.findToolAppFirstScrollView(root.getChildAt(i));
if (found) return found;
}
} catch(e) {}
return null;
};
FloatBallAppWM.prototype.captureToolAppCurrentScrollY = function() {
try {
var host = this.state ? this.state.toolAppContentHost : null;
var sv = this.findToolAppFirstScrollView ? this.findToolAppFirstScrollView(host) : null;
if (!sv) return null;
var y = Number(sv.getScrollY ? sv.getScrollY() : 0);
if (isNaN(y) || y < 0) y = 0;
return Math.floor(y);
} catch(e) {}
return null;
};
FloatBallAppWM.prototype.saveToolAppCurrentStackScroll = function() {
try {
var st = this.state.toolAppNavStack;
if (!st || !st.length) return false;
var y = this.captureToolAppCurrentScrollY ? this.captureToolAppCurrentScrollY() : null;
if (y === null || y === undefined) return false;
st[st.length - 1].toolAppScrollY = y;
this.state.toolAppScrollY = y;
return true;
} catch(e) {}
return false;
};
FloatBallAppWM.prototype.restoreToolAppScrollLater = function(root, entry, hideUntilRestored) {
try {
if (!root) return false;
var y = 0;
if (entry && entry.toolAppScrollY !== undefined && entry.toolAppScrollY !== null) y = Number(entry.toolAppScrollY);
else if (this.state && this.state.toolAppScrollY !== undefined && this.state.toolAppScrollY !== null) y = Number(this.state.toolAppScrollY);
if (isNaN(y) || y < 0) y = 0;
y = Math.floor(y);
if (y <= 0) return false;
var self = this;
var shouldHide = !!hideUntilRestored;
if (shouldHide) {
try { root.setAlpha(0.01); } catch(eHide) {}
}
root.post(new java.lang.Runnable({ run: function() {
try {
var sv = self.findToolAppFirstScrollView ? self.findToolAppFirstScrollView(root) : null;
if (sv) sv.scrollTo(0, y);
} catch(ePost) {}
try {
root.post(new java.lang.Runnable({ run: function() {
try {
var sv2 = self.findToolAppFirstScrollView ? self.findToolAppFirstScrollView(root) : null;
if (sv2) sv2.scrollTo(0, y);
} catch(ePost2) {}
if (shouldHide) {
try { root.setAlpha(1.0); } catch(eShow) {}
}
}}));
} catch(ePostAgain) {
if (shouldHide) {
try { root.setAlpha(1.0); } catch(eShow2) {}
}
}
}}));
return true;
} catch(e) {
try { if (root) root.setAlpha(1.0); } catch(eRestoreAlpha) {}
}
return false;
};
FloatBallAppWM.prototype.captureToolAppPageSnapshot = function(route) { FloatBallAppWM.prototype.captureToolAppPageSnapshot = function(route) {
var r = this.isToolAppRoute(route) ? String(route) : (this.isToolAppRoute(this.state.toolAppRoute) ? String(this.state.toolAppRoute) : "settings"); var r = this.isToolAppRoute(route) ? String(route) : (this.isToolAppRoute(this.state.toolAppRoute) ? String(this.state.toolAppRoute) : "settings");
var s = this.state || {}; var s = this.state || {};
var liveScrollY = null;
try {
if (String(s.toolAppRoute || "") === r && this.captureToolAppCurrentScrollY) liveScrollY = this.captureToolAppCurrentScrollY();
} catch(eLiveScroll) { liveScrollY = null; }
var entry = { var entry = {
route: r, route: r,
settingsGroupKey: (s.settingsGroupKey !== undefined && s.settingsGroupKey !== null) ? String(s.settingsGroupKey) : "", settingsGroupKey: (s.settingsGroupKey !== undefined && s.settingsGroupKey !== null) ? String(s.settingsGroupKey) : "",
@@ -622,13 +770,16 @@ FloatBallAppWM.prototype.captureToolAppPageSnapshot = function(route) {
editingSchemaIndex: (s.editingSchemaIndex !== undefined) ? s.editingSchemaIndex : null, editingSchemaIndex: (s.editingSchemaIndex !== undefined) ? s.editingSchemaIndex : null,
keepBtnEditorState: !!s.keepBtnEditorState, keepBtnEditorState: !!s.keepBtnEditorState,
keepSchemaEditorState: !!s.keepSchemaEditorState, keepSchemaEditorState: !!s.keepSchemaEditorState,
settingsHomeSelectedCategoryId: (s.settingsHomeSelectedCategoryId !== undefined) ? s.settingsHomeSelectedCategoryId : null,
toolAppSubRoute: (s.toolAppSubRoute !== undefined) ? s.toolAppSubRoute : null, toolAppSubRoute: (s.toolAppSubRoute !== undefined) ? s.toolAppSubRoute : null,
toolAppSubPage: (s.toolAppSubPage !== undefined) ? s.toolAppSubPage : null, toolAppSubPage: (s.toolAppSubPage !== undefined) ? s.toolAppSubPage : null,
toolAppSubKey: (s.toolAppSubKey !== undefined) ? s.toolAppSubKey : null, toolAppSubKey: (s.toolAppSubKey !== undefined) ? s.toolAppSubKey : null,
toolAppSubPayload: (s.toolAppSubPayload !== undefined) ? s.toolAppSubPayload : null toolAppSubPayload: (s.toolAppSubPayload !== undefined) ? this.cloneToolAppSnapshotValue(s.toolAppSubPayload, 0) : null,
toolAppPayload: (s.toolAppPayload !== undefined) ? this.cloneToolAppSnapshotValue(s.toolAppPayload, 0) : null,
toolAppScrollY: (String(s.toolAppRoute || "") === r) ? ((liveScrollY !== null && liveScrollY !== undefined) ? liveScrollY : ((s.toolAppScrollY !== undefined) ? s.toolAppScrollY : 0)) : 0
}; };
if (r !== "settings_group") entry.settingsGroupKey = ""; if (r !== "settings_group") entry.settingsGroupKey = "";
if (r !== "settings") entry.settingsHomeSelectedItemId = null; if (r !== "settings") { entry.settingsHomeSelectedItemId = null; entry.settingsHomeSelectedCategoryId = null; }
if (r !== "btn_editor") { entry.editingButtonIndex = null; entry.keepBtnEditorState = false; } if (r !== "btn_editor") { entry.editingButtonIndex = null; entry.keepBtnEditorState = false; }
if (r !== "schema_editor") { entry.editingSchemaIndex = null; entry.keepSchemaEditorState = false; } if (r !== "schema_editor") { entry.editingSchemaIndex = null; entry.keepSchemaEditorState = false; }
return entry; return entry;
@@ -645,6 +796,7 @@ FloatBallAppWM.prototype.applyToolAppPageSnapshot = function(entry) {
this.state.toolAppRoute = r; this.state.toolAppRoute = r;
this.state.settingsGroupKey = (entry.settingsGroupKey !== undefined && entry.settingsGroupKey !== null) ? String(entry.settingsGroupKey) : ""; this.state.settingsGroupKey = (entry.settingsGroupKey !== undefined && entry.settingsGroupKey !== null) ? String(entry.settingsGroupKey) : "";
this.state.settingsHomeSelectedItemId = (entry.settingsHomeSelectedItemId !== undefined) ? entry.settingsHomeSelectedItemId : null; this.state.settingsHomeSelectedItemId = (entry.settingsHomeSelectedItemId !== undefined) ? entry.settingsHomeSelectedItemId : null;
this.state.settingsHomeSelectedCategoryId = (entry.settingsHomeSelectedCategoryId !== undefined) ? entry.settingsHomeSelectedCategoryId : null;
this.state.editingButtonIndex = (entry.editingButtonIndex !== undefined) ? entry.editingButtonIndex : null; this.state.editingButtonIndex = (entry.editingButtonIndex !== undefined) ? entry.editingButtonIndex : null;
this.state.editingSchemaIndex = (entry.editingSchemaIndex !== undefined) ? entry.editingSchemaIndex : null; this.state.editingSchemaIndex = (entry.editingSchemaIndex !== undefined) ? entry.editingSchemaIndex : null;
this.state.keepBtnEditorState = !!entry.keepBtnEditorState; this.state.keepBtnEditorState = !!entry.keepBtnEditorState;
@@ -652,7 +804,10 @@ FloatBallAppWM.prototype.applyToolAppPageSnapshot = function(entry) {
this.state.toolAppSubRoute = (entry.toolAppSubRoute !== undefined) ? entry.toolAppSubRoute : null; this.state.toolAppSubRoute = (entry.toolAppSubRoute !== undefined) ? entry.toolAppSubRoute : null;
this.state.toolAppSubPage = (entry.toolAppSubPage !== undefined) ? entry.toolAppSubPage : null; this.state.toolAppSubPage = (entry.toolAppSubPage !== undefined) ? entry.toolAppSubPage : null;
this.state.toolAppSubKey = (entry.toolAppSubKey !== undefined) ? entry.toolAppSubKey : null; this.state.toolAppSubKey = (entry.toolAppSubKey !== undefined) ? entry.toolAppSubKey : null;
this.state.toolAppSubPayload = (entry.toolAppSubPayload !== undefined) ? entry.toolAppSubPayload : null; this.state.toolAppSubPayload = (entry.toolAppSubPayload !== undefined) ? this.cloneToolAppSnapshotValue(entry.toolAppSubPayload, 0) : null;
this.state.toolAppPayload = (entry.toolAppPayload !== undefined) ? this.cloneToolAppSnapshotValue(entry.toolAppPayload, 0) : null;
this.state.toolAppScrollY = (entry.toolAppScrollY !== undefined && entry.toolAppScrollY !== null) ? Number(entry.toolAppScrollY) : 0;
if (isNaN(this.state.toolAppScrollY) || this.state.toolAppScrollY < 0) this.state.toolAppScrollY = 0;
return true; return true;
} catch(e) { safeLog(this.L, 'w', "apply tool app snapshot fail: " + String(e)); } } catch(e) { safeLog(this.L, 'w', "apply tool app snapshot fail: " + String(e)); }
return false; return false;
@@ -668,10 +823,13 @@ FloatBallAppWM.prototype.cloneToolAppPageSnapshot = function(entry) {
editingSchemaIndex: (entry.editingSchemaIndex !== undefined) ? entry.editingSchemaIndex : null, editingSchemaIndex: (entry.editingSchemaIndex !== undefined) ? entry.editingSchemaIndex : null,
keepBtnEditorState: !!entry.keepBtnEditorState, keepBtnEditorState: !!entry.keepBtnEditorState,
keepSchemaEditorState: !!entry.keepSchemaEditorState, keepSchemaEditorState: !!entry.keepSchemaEditorState,
settingsHomeSelectedCategoryId: (entry.settingsHomeSelectedCategoryId !== undefined) ? entry.settingsHomeSelectedCategoryId : null,
toolAppSubRoute: (entry.toolAppSubRoute !== undefined) ? entry.toolAppSubRoute : null, toolAppSubRoute: (entry.toolAppSubRoute !== undefined) ? entry.toolAppSubRoute : null,
toolAppSubPage: (entry.toolAppSubPage !== undefined) ? entry.toolAppSubPage : null, toolAppSubPage: (entry.toolAppSubPage !== undefined) ? entry.toolAppSubPage : null,
toolAppSubKey: (entry.toolAppSubKey !== undefined) ? entry.toolAppSubKey : null, toolAppSubKey: (entry.toolAppSubKey !== undefined) ? entry.toolAppSubKey : null,
toolAppSubPayload: (entry.toolAppSubPayload !== undefined) ? entry.toolAppSubPayload : null toolAppSubPayload: (entry.toolAppSubPayload !== undefined) ? this.cloneToolAppSnapshotValue(entry.toolAppSubPayload, 0) : null,
toolAppPayload: (entry.toolAppPayload !== undefined) ? this.cloneToolAppSnapshotValue(entry.toolAppPayload, 0) : null,
toolAppScrollY: (entry.toolAppScrollY !== undefined && entry.toolAppScrollY !== null) ? Number(entry.toolAppScrollY) : 0
}; };
}; };
@@ -694,6 +852,9 @@ FloatBallAppWM.prototype.buildToolAppPreviewBody = function(entry) {
} catch(eSpec) { } catch(eSpec) {
spec = null; spec = null;
} }
var shellPad = spec ? spec.shellPadding : this.dp(6);
var shellTopPad = shellPad;
var outerRadius = spec ? spec.outerRadius : this.dp(26);
var topBarHeight = spec ? spec.topBarHeight : this.dp(56); var topBarHeight = spec ? spec.topBarHeight : this.dp(56);
var isDark = this.isDarkTheme(); var isDark = this.isDarkTheme();
@@ -704,8 +865,8 @@ FloatBallAppWM.prototype.buildToolAppPreviewBody = function(entry) {
var body = new android.widget.LinearLayout(context); var body = new android.widget.LinearLayout(context);
body.setOrientation(android.widget.LinearLayout.VERTICAL); body.setOrientation(android.widget.LinearLayout.VERTICAL);
body.setPadding(this.dp(6), this.dp(6), this.dp(6), this.dp(8)); body.setPadding(shellPad, shellTopPad, shellPad, shellPad);
body.setBackground(this.ui.createStrokeDrawable(T.bg, this.withAlpha(T.stroke, isDark ? 0.42 : 0.70), this.dp(1), this.dp(26))); body.setBackground(this.ui.createStrokeDrawable(T.bg, this.withAlpha(T.stroke, isDark ? 0.30 : 0.46), this.dp(1), outerRadius));
try { body.setClipToOutline(true); } catch(eClip) {} try { body.setClipToOutline(true); } catch(eClip) {}
try { body.setElevation(this.dp((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? 7 : 10)); } catch (eElev) {} try { body.setElevation(this.dp((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? 7 : 10)); } catch (eElev) {}
@@ -759,7 +920,7 @@ FloatBallAppWM.prototype.buildToolAppPreviewBody = function(entry) {
try { btnClose.setBackground(this.ui.createStrokeDrawable(T.primarySoft, this.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), this.dp(1), this.dp(18))); } catch(eRightBg) {} try { btnClose.setBackground(this.ui.createStrokeDrawable(T.primarySoft, this.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), this.dp(1), this.dp(18))); } catch(eRightBg) {}
bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(104), this.dp(38))); bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(104), this.dp(38)));
var barLp = new android.widget.LinearLayout.LayoutParams(-1, topBarHeight); var barLp = new android.widget.LinearLayout.LayoutParams(-1, topBarHeight);
barLp.setMargins(this.dp(8), this.dp(8), this.dp(8), this.dp(4)); barLp.setMargins(this.dp(8), this.dp(2), this.dp(8), this.dp(4));
body.addView(bar, barLp); body.addView(bar, barLp);
var host = new android.widget.FrameLayout(context); var host = new android.widget.FrameLayout(context);
@@ -767,6 +928,7 @@ FloatBallAppWM.prototype.buildToolAppPreviewBody = function(entry) {
try { raw.setBackground(null); } catch (eBg) {} try { raw.setBackground(null); } catch (eBg) {}
try { raw.setElevation(0); } catch (eEl) {} try { raw.setElevation(0); } catch (eEl) {}
host.addView(raw, new android.widget.FrameLayout.LayoutParams(-1, -1)); host.addView(raw, new android.widget.FrameLayout.LayoutParams(-1, -1));
try { if (this.restoreToolAppScrollLater) this.restoreToolAppScrollLater(raw, entry); } catch(ePreviewScroll) {}
var hostLp = new android.widget.LinearLayout.LayoutParams(-1, 0, 1); var hostLp = new android.widget.LinearLayout.LayoutParams(-1, 0, 1);
hostLp.setMargins((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), 0, (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6)); hostLp.setMargins((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), 0, (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6));
body.addView(host, hostLp); body.addView(host, hostLp);
@@ -783,7 +945,7 @@ FloatBallAppWM.prototype.buildToolAppPreviewBody = function(entry) {
FloatBallAppWM.prototype.prepareToolAppBackPreview = function(edge) { FloatBallAppWM.prototype.prepareToolAppBackPreview = function(edge) {
try { try {
if (this.state.toolAppBackPreviewReady) return true; var stackVersion = this.getToolAppStackVersion ? this.getToolAppStackVersion() : Number(this.state.toolAppNavStackVersion || 0);
var root = this.state.toolAppRoot; var root = this.state.toolAppRoot;
var body = this.state.toolAppBody; var body = this.state.toolAppBody;
var prevEntry = this.getToolAppPreviousStackEntry(); var prevEntry = this.getToolAppPreviousStackEntry();
@@ -793,6 +955,12 @@ FloatBallAppWM.prototype.prepareToolAppBackPreview = function(edge) {
isPaneBack = true; isPaneBack = true;
} }
if (!root || !body || !prevEntry || !prevEntry.route) return false; if (!root || !body || !prevEntry || !prevEntry.route) return false;
prevEntry = this.cloneToolAppPageSnapshot ? this.cloneToolAppPageSnapshot(prevEntry) : prevEntry;
var prevEntryKey = this.getToolAppSnapshotKey ? this.getToolAppSnapshotKey(prevEntry) : String(prevEntry.route || "settings");
if (this.state.toolAppBackPreviewReady) {
if (this.state.toolAppBackPreviewStackVersion === stackVersion && this.state.toolAppBackPreviewEntryKey === prevEntryKey) return true;
this.clearToolAppBackPreview(false);
}
var prevRoute = isPaneBack ? "settings:pane" : String(prevEntry.route || "settings"); var prevRoute = isPaneBack ? "settings:pane" : String(prevEntry.route || "settings");
var oldPaneItem = null; var oldPaneItem = null;
var hasOldPaneItem = false; var hasOldPaneItem = false;
@@ -825,6 +993,8 @@ FloatBallAppWM.prototype.prepareToolAppBackPreview = function(edge) {
this.state.toolAppBackPreviewView = prevBody; this.state.toolAppBackPreviewView = prevBody;
this.state.toolAppBackPreviewRoute = prevRoute; this.state.toolAppBackPreviewRoute = prevRoute;
this.state.toolAppBackPreviewReady = true; this.state.toolAppBackPreviewReady = true;
this.state.toolAppBackPreviewStackVersion = stackVersion;
this.state.toolAppBackPreviewEntryKey = prevEntryKey;
return true; return true;
} catch (e) { } catch (e) {
safeLog(this.L, 'w', "prepare tool app back preview fail: " + String(e)); safeLog(this.L, 'w', "prepare tool app back preview fail: " + String(e));
@@ -944,10 +1114,9 @@ FloatBallAppWM.prototype.applyToolAppBackPreviewProgress = function(edge, progre
if (body) { if (body) {
try { body.animate().cancel(); } catch(eCancelBody) {} try { body.animate().cancel(); } catch(eCancelBody) {}
body.setTranslationX(bodyMove); body.setTranslationX(bodyMove);
body.setAlpha(1.0 - 0.10 * eased); body.setAlpha(1.0);
var s = 1.0 - 0.015 * eased; body.setScaleX(1.0);
body.setScaleX(s); body.setScaleY(1.0);
body.setScaleY(s);
} }
try { try {
@@ -969,11 +1138,10 @@ FloatBallAppWM.prototype.applyToolAppBackPreviewProgress = function(edge, progre
} }
} catch(eMoveLog) {} } catch(eMoveLog) {}
if (prev) { if (prev) {
prev.setAlpha(0.88 + 0.12 * eased); prev.setAlpha(1.0);
prev.setTranslationX(-dir * this.dp(24) * (1.0 - eased)); prev.setTranslationX(0);
var ps = 0.975 + 0.025 * eased; prev.setScaleX(1.0);
prev.setScaleX(ps); prev.setScaleY(1.0);
prev.setScaleY(ps);
} }
return true; return true;
} catch (e) { safeLog(this.L, 'w', "apply tool app back preview fail: " + String(e)); } } catch (e) { safeLog(this.L, 'w', "apply tool app back preview fail: " + String(e)); }
@@ -995,10 +1163,19 @@ FloatBallAppWM.prototype.finishToolAppBackPreview = function(edge, complete) {
try { w = Number((this.state.toolAppRoot && this.state.toolAppRoot.getWidth && this.state.toolAppRoot.getWidth()) || 0); } catch (eW1) {} try { w = Number((this.state.toolAppRoot && this.state.toolAppRoot.getWidth && this.state.toolAppRoot.getWidth()) || 0); } catch (eW1) {}
} }
if (!w || w < this.dp(120)) w = this.dp(320); if (!w || w < this.dp(120)) w = this.dp(320);
try { if (prev) prev.animate().translationX(0).alpha(1).scaleX(1).scaleY(1).setDuration(180).setInterpolator(decel).start(); } catch(ePrev) {} try { if (prev) prev.animate().translationX(0).alpha(1).scaleX(1).scaleY(1).setDuration(120).setInterpolator(decel).start(); } catch(ePrev) {}
body.animate().translationX(dir * w).alpha(0.90).scaleX(0.985).scaleY(0.985).setDuration(180).setInterpolator(decel).withEndAction(new java.lang.Runnable({ body.animate().translationX(dir * w).alpha(1).scaleX(1).scaleY(1).setDuration(160).setInterpolator(decel).withEndAction(new java.lang.Runnable({
run: function() { run: function() {
try { self.resetToolAppBackWindowFollow(); } catch(eResetFollow) {} try { self.resetToolAppBackWindowFollow(); } catch(eResetFollow) {}
try { self.state.keepToolAppBackPreviewDuringPop = true; } catch(eKeepPreview) {}
try { self.popToolAppPage("edge_swipe_back"); } catch (ePop) {}
try { self.state.keepToolAppBackPreviewDuringPop = false; } catch(eKeepPreview2) {}
try {
var rootAfter = self.state.toolAppRoot;
if (rootAfter) {
rootAfter.post(new java.lang.Runnable({ run: function() {
try {
rootAfter.post(new java.lang.Runnable({ run: function() {
try { try {
if (self.state.toolAppRoot) self.state.toolAppRoot.setTranslationX(0); if (self.state.toolAppRoot) self.state.toolAppRoot.setTranslationX(0);
if (self.state.toolAppBody) { if (self.state.toolAppBody) {
@@ -1007,16 +1184,26 @@ FloatBallAppWM.prototype.finishToolAppBackPreview = function(edge, complete) {
self.state.toolAppBody.setScaleX(1); self.state.toolAppBody.setScaleX(1);
self.state.toolAppBody.setScaleY(1); self.state.toolAppBody.setScaleY(1);
} }
} catch(eResetView) {} } catch(eResetViewLater) {}
try { self.clearToolAppBackPreview(true); } catch (eClear) {} try { self.clearToolAppBackPreview(true); } catch (eClearLater) {}
try { self.popToolAppPage("edge_swipe_back"); } catch (ePop) {} }}));
} catch(ePost2) {
try { self.clearToolAppBackPreview(true); } catch (eClearPostFail) {}
}
}}));
} else {
try { self.clearToolAppBackPreview(true); } catch (eClearNoRoot) {}
}
} catch(eLater) {
try { self.clearToolAppBackPreview(true); } catch (eClearLaterFail) {}
}
} }
})).start(); })).start();
return; return;
} }
if (body) { if (body) {
var cancelInterp = new android.view.animation.AccelerateDecelerateInterpolator(); var cancelInterp = new android.view.animation.AccelerateDecelerateInterpolator();
try { if (prev) prev.animate().translationX(-dir * self.dp(24)).alpha(0.88).scaleX(0.975).scaleY(0.975).setDuration(200).setInterpolator(cancelInterp).start(); } catch(ePrev2) {} try { if (prev) prev.animate().translationX(0).alpha(1).scaleX(1).scaleY(1).setDuration(160).setInterpolator(cancelInterp).start(); } catch(ePrev2) {}
body.animate().translationX(0).alpha(1).scaleX(1).scaleY(1).setDuration(200).setInterpolator(cancelInterp).withEndAction(new java.lang.Runnable({ body.animate().translationX(0).alpha(1).scaleX(1).scaleY(1).setDuration(200).setInterpolator(cancelInterp).withEndAction(new java.lang.Runnable({
run: function() { run: function() {
try { self.resetToolAppBackWindowFollow(); } catch(eResetFollow2) {} try { self.resetToolAppBackWindowFollow(); } catch(eResetFollow2) {}
@@ -1044,121 +1231,19 @@ FloatBallAppWM.prototype.finishToolAppBackPreview = function(edge, complete) {
} }
}; };
FloatBallAppWM.prototype.createToolAppEdgeBackStrip = function(edge) {
var self = this;
var strip = new android.view.View(context);
strip.setBackgroundColor(android.graphics.Color.TRANSPARENT);
var downX = 0;
var downY = 0;
var active = false;
var moved = false;
strip.setOnTouchListener(new android.view.View.OnTouchListener({
onTouch: function(v, event) {
try {
if (!event) return false;
var action = event.getActionMasked();
if (action === android.view.MotionEvent.ACTION_DOWN) {
downX = event.getRawX();
downY = event.getRawY();
active = !!(self.state && self.state.toolAppActive && self.hasToolAppBackTarget && self.hasToolAppBackTarget());
moved = false;
if (active) self.prepareToolAppBackPreview(edge);
return active;
}
if (!active) return false;
if (action === android.view.MotionEvent.ACTION_MOVE) {
var mx = event.getRawX() - downX;
var my = event.getRawY() - downY;
var validDir = (edge === 0 && mx > 0) || (edge === 1 && mx < 0);
if (validDir && Math.abs(mx) > self.dp(4) && Math.abs(mx) > Math.abs(my)) {
if (!moved) {
try { safeLog(self.L, 'd', 'edge strip move edge=' + String(edge) + ' mx=' + String(mx)); } catch(eMoveLog) {}
}
moved = true;
var triggerDp = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 180);
if (isNaN(triggerDp)) triggerDp = 180;
if (triggerDp < 1) triggerDp = 1;
if (triggerDp > 720) triggerDp = 720;
var triggerDistance = self.dp(triggerDp);
var p = Math.min(1, Math.abs(mx) / triggerDistance);
self.applyToolAppBackPreviewProgress(edge, p, Math.abs(mx));
}
return true;
}
if (action === android.view.MotionEvent.ACTION_UP || action === android.view.MotionEvent.ACTION_CANCEL) {
var ux = event.getRawX() - downX;
var uy = event.getRawY() - downY;
var commitDp = Number(self.config.TOOLAPP_BACK_COMMIT_DISTANCE_DP || 72);
if (isNaN(commitDp)) commitDp = 72;
if (commitDp < 1) commitDp = 1;
if (commitDp > 480) commitDp = 480;
var completeDistance = self.dp(commitDp);
var okDir = (edge === 0 && ux > completeDistance) || (edge === 1 && ux < -completeDistance);
var ok = (action === android.view.MotionEvent.ACTION_UP) && moved && okDir && Math.abs(ux) > Math.abs(uy) * 1.2;
active = false;
self.finishToolAppBackPreview(edge, ok);
return true;
}
} catch (e) {
try { self.clearToolAppBackPreview(true); } catch (eClear) {}
try { safeLog(self.L, 'w', "tool app edge back fail: " + String(e)); } catch(eLog) {}
}
return false;
}
}));
return strip;
};
FloatBallAppWM.prototype.hideToolAppScreenBackStrips = function() {
try {
var arr = this.state.toolAppScreenBackStrips || [];
for (var i = 0; i < arr.length; i++) {
try { if (arr[i] && this.state.wm) this.state.wm.removeView(arr[i]); } catch (eRm) {}
}
this.state.toolAppScreenBackStrips = [];
} catch (e) { safeLog(this.L, 'w', "hide tool app screen back strips fail: " + String(e)); }
};
FloatBallAppWM.prototype.getToolAppBackEdgeWidthPx = function() { FloatBallAppWM.prototype.getToolAppBackEdgeWidthPx = function() {
var stripDp = 24; var stripDp = 72;
try { try {
stripDp = Number(this.config.TOOLAPP_BACK_EDGE_WIDTH_DP || 24); stripDp = Number(this.config.TOOLAPP_BACK_EDGE_WIDTH_DP || 72);
if (isNaN(stripDp)) stripDp = 24; if (isNaN(stripDp)) stripDp = 72;
if (stripDp < 1) stripDp = 1; if (stripDp < 1) stripDp = 1;
if (stripDp > 120) stripDp = 120; if (stripDp > 120) stripDp = 120;
} catch(e) { } catch(e) {
stripDp = 24; stripDp = 72;
} }
return this.dp(stripDp); return this.dp(stripDp);
}; };
FloatBallAppWM.prototype.isToolAppScreenBackStripsEnabled = function() {
try { return parseBooleanLike(this.config.ENABLE_TOOLAPP_SCREEN_BACK_STRIPS); } catch(e) {}
try { return String(this.config.ENABLE_TOOLAPP_SCREEN_BACK_STRIPS || "false") === "true"; } catch(e2) {}
return false;
};
FloatBallAppWM.prototype.showToolAppScreenBackStrips = function() {
// 兼容旧配置入口:不再创建覆盖 ToolApp 内容区的 MATCH_PARENT 透明触摸层。
// 主要返回手势已迁移到 ToolApp root 的 onInterceptTouchEvent避免遮挡列表/按钮/SeekBar/Switch。
try { this.hideToolAppScreenBackStrips(); } catch(eHide) {}
return false;
};
FloatBallAppWM.prototype.refreshToolAppScreenBackStrips = function() {
try {
this.hideToolAppScreenBackStrips();
if (!this.state.toolAppActive) return false;
if (!this.hasToolAppBackTarget || !this.hasToolAppBackTarget()) return false;
if (!this.isToolAppScreenBackStripsEnabled || !this.isToolAppScreenBackStripsEnabled()) return false;
return this.showToolAppScreenBackStrips();
} catch(e) {
try { this.hideToolAppScreenBackStrips(); } catch(eHide) {}
safeLog(this.L, 'w', "refresh tool app screen back strips fail: " + String(e));
}
return false;
};
FloatBallAppWM.prototype.getToolAppResponsiveSpec = function() { FloatBallAppWM.prototype.getToolAppResponsiveSpec = function() {
var sw = Math.max(1, Number(this.state.screen && this.state.screen.w || 0)); var sw = Math.max(1, Number(this.state.screen && this.state.screen.w || 0));
var sh = Math.max(1, Number(this.state.screen && this.state.screen.h || 0)); var sh = Math.max(1, Number(this.state.screen && this.state.screen.h || 0));
@@ -1187,6 +1272,97 @@ FloatBallAppWM.prototype.getToolAppResponsiveSpec = function() {
}; };
}; };
FloatBallAppWM.prototype.getToolAppBackGestureMode = function() {
var mode = "surface";
try { mode = String(this.config.TOOLAPP_BACK_GESTURE_MODE || "surface"); } catch(e) { mode = "surface"; }
if (mode !== "edge" && mode !== "surface" && mode !== "off") mode = "surface";
// 全面屏手势下物理极致边缘容易被系统抢走;旧配置若停留在 edge运行期优先回退到 surface。
// edge 分支仍保留为 ToolApp 面板内部边缘模式;只有显式把 TOOLAPP_BACK_FORCE_SURFACE 设为 false 时才启用。
if (mode === "edge") {
var forceSurface = true;
try { if (this.config.TOOLAPP_BACK_FORCE_SURFACE === false || String(this.config.TOOLAPP_BACK_FORCE_SURFACE) === "false") forceSurface = false; } catch(eForce) {}
if (forceSurface) {
try { this.config.TOOLAPP_BACK_GESTURE_MODE = "surface"; } catch(eMig) {}
mode = "surface";
}
}
return mode;
};
FloatBallAppWM.prototype.getToolAppBackSurfaceSlopPx = function(commitDistancePx) {
var slopDp = 24;
try { slopDp = Number(this.config.TOOLAPP_BACK_SURFACE_SLOP_DP || 24); } catch(e) { slopDp = 24; }
if (isNaN(slopDp)) slopDp = 24;
if (slopDp < 8) slopDp = 8;
if (slopDp > 96) slopDp = 96;
var px = this.dp(slopDp);
try {
var c = Number(commitDistancePx || 0);
if (!isNaN(c) && c > 0) px = Math.min(px, c);
} catch(e2) {}
return px;
};
FloatBallAppWM.prototype.isToolAppBackInteractiveView = function(v, dx, dy) {
try {
if (!v) return false;
var adx = Math.abs(Number(dx || 0));
var ady = Math.abs(Number(dy || 0));
var strongHorizontal = adx > 0 && adx >= ady;
try { if (v instanceof android.widget.SeekBar) return strongHorizontal; } catch(eSeek) {}
try { if (v instanceof android.widget.CompoundButton) return strongHorizontal; } catch(eComp) {}
try { if (v instanceof android.widget.Switch) return strongHorizontal; } catch(eSw) {}
try { if (v instanceof android.widget.EditText) return true; } catch(eEdit) {}
try {
if (v instanceof android.widget.HorizontalScrollView) {
var dir = Number(dx || 0) > 0 ? -1 : 1;
try { if (v.canScrollHorizontally && v.canScrollHorizontally(dir)) return true; } catch(eCan) {}
return false;
}
} catch(eHsv) {}
// 普通 Button / 卡片 / 垂直列表 item 不作为 blocker点击和上下滑继续交给子控件强横滑由 root 接管。
// 只有 SeekBar/Switch/EditText/可继续横向滚动的 HorizontalScrollView 在 MOVE 阶段按 dx/dy 细粒度阻断。
/* try { if (v.isClickable && v.isClickable()) return true; } catch(eClick) {} */
/* try { if (v.isLongClickable && v.isLongClickable()) return true; } catch(eLong) {} */
} catch(e) {}
return false;
};
FloatBallAppWM.prototype.findToolAppTouchedChild = function(v, rawX, rawY) {
try {
if (!v || !v.getVisibility || v.getVisibility() !== android.view.View.VISIBLE) return null;
var loc = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, 2);
try { v.getLocationOnScreen(loc); } catch(eLoc) { return null; }
var l = Number(loc[0] || 0), t = Number(loc[1] || 0);
var r = l + Number(v.getWidth ? v.getWidth() : 0);
var b = t + Number(v.getHeight ? v.getHeight() : 0);
if (rawX < l || rawX > r || rawY < t || rawY > b) return null;
try {
if (v instanceof android.view.ViewGroup) {
var count = v.getChildCount ? v.getChildCount() : 0;
for (var i = count - 1; i >= 0; i--) {
var child = v.getChildAt(i);
var hit = this.findToolAppTouchedChild(child, rawX, rawY);
if (hit) return hit;
}
}
} catch(eGroup) {}
return v;
} catch(e) {}
return null;
};
FloatBallAppWM.prototype.isToolAppBackBlockedAt = function(root, rawX, rawY, dx, dy) {
try {
var v = this.findToolAppTouchedChild(root, rawX, rawY);
while (v && v !== root) {
if (this.isToolAppBackInteractiveView && this.isToolAppBackInteractiveView(v, dx, dy)) return true;
try { v = v.getParent ? v.getParent() : null; } catch(eParent) { v = null; }
}
} catch(e) {}
return false;
};
FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBack) { FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBack) {
var self = this; var self = this;
var isDark = this.isDarkTheme(); var isDark = this.isDarkTheme();
@@ -1196,12 +1372,20 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
try { if (this.applySettingsTheme) this.applySettingsTheme(T, isDark, C, cfgTpl); } catch(eTheme) { safeLog(null, 'e', "catch " + String(eTheme)); } try { if (this.applySettingsTheme) this.applySettingsTheme(T, isDark, C, cfgTpl); } catch(eTheme) { safeLog(null, 'e', "catch " + String(eTheme)); }
var spec = this.getToolAppResponsiveSpec ? this.getToolAppResponsiveSpec() : null; var spec = this.getToolAppResponsiveSpec ? this.getToolAppResponsiveSpec() : null;
var shellPad = spec ? spec.shellPadding : this.dp(6); var shellPad = spec ? spec.shellPadding : this.dp(6);
var shellTopPad = shellPad;
// ToolApp 是 WindowManager 浮层卡片,不需要额外叠加状态栏 inset
// 否则一级/二级页面标题上方会出现一块空白。
var outerRadius = spec ? spec.outerRadius : this.dp(26); var outerRadius = spec ? spec.outerRadius : this.dp(26);
var topBarHeight = spec ? spec.topBarHeight : this.dp(56); var topBarHeight = spec ? spec.topBarHeight : this.dp(56);
var rootDownX = 0; var rootDownX = 0;
var rootDownY = 0; var rootDownY = 0;
var rootDownRawX = 0;
var rootDownRawY = 0;
var rootEdge = -1; var rootEdge = -1;
var rootBackMode = "surface";
var rootBackActive = false; var rootBackActive = false;
var rootBackEligible = false;
var rootBackBlocked = false;
var rootBackMoved = false; var rootBackMoved = false;
var root = new JavaAdapter(android.widget.FrameLayout, { var root = new JavaAdapter(android.widget.FrameLayout, {
onInterceptTouchEvent: function(ev) { onInterceptTouchEvent: function(ev) {
@@ -1211,37 +1395,78 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
if (action === android.view.MotionEvent.ACTION_DOWN) { if (action === android.view.MotionEvent.ACTION_DOWN) {
rootDownX = ev.getX(); rootDownX = ev.getX();
rootDownY = ev.getY(); rootDownY = ev.getY();
rootDownRawX = ev.getRawX();
rootDownRawY = ev.getRawY();
rootBackActive = false; rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false; rootBackMoved = false;
rootEdge = -1; rootEdge = -1;
rootBackMode = self.getToolAppBackGestureMode ? self.getToolAppBackGestureMode() : "surface";
if (rootBackMode === "off") return false;
var canBackNow = !!(self.state && self.state.toolAppActive && self.hasToolAppBackTarget && self.hasToolAppBackTarget()); var canBackNow = !!(self.state && self.state.toolAppActive && self.hasToolAppBackTarget && self.hasToolAppBackTarget());
if (canBackNow) { if (!canBackNow) return false;
var edgeW = self.getToolAppBackEdgeWidthPx ? self.getToolAppBackEdgeWidthPx() : self.dp(24); var edgeW = self.getToolAppBackEdgeWidthPx ? self.getToolAppBackEdgeWidthPx() : self.dp(72);
var rw = 0; var rw = 0;
try { rw = this.getWidth(); } catch(eW) { rw = 0; } try { rw = this.getWidth(); } catch(eW) { rw = 0; }
if (rootDownX <= edgeW) rootEdge = 0; if (rootBackMode === "edge") {
else if (rw > 0 && rootDownX >= rw - edgeW) rootEdge = 1; if (rootDownX <= edgeW) { rootEdge = 0; rootBackEligible = true; }
else if (rw > 0 && rootDownX >= rw - edgeW) { rootEdge = 1; rootBackEligible = true; }
} else {
rootBackEligible = true;
rootBackBlocked = false;
} }
// DOWN 必须放行给子控件,避免按钮/列表/Switch/SeekBar 被边缘手势抢走 // DOWN 必须放行给子控件,surface 模式也不抢按钮/列表/Switch/SeekBar 原始点击
return false; return false;
} }
if (action === android.view.MotionEvent.ACTION_MOVE) { if (action === android.view.MotionEvent.ACTION_MOVE) {
if (rootEdge < 0) return false; if (!rootBackEligible || rootBackMode === "off") return false;
if (!(self.hasToolAppBackTarget && self.hasToolAppBackTarget())) return false;
var dx = ev.getX() - rootDownX; var dx = ev.getX() - rootDownX;
var dy = ev.getY() - rootDownY; var dy = ev.getY() - rootDownY;
var adx = Math.abs(dx); var adx = Math.abs(dx);
var ady = Math.abs(dy); var ady = Math.abs(dy);
var validDir = (rootEdge === 0 && dx > 0) || (rootEdge === 1 && dx < 0); var edge = rootEdge;
var slopDp = Number(self.config.CLICK_SLOP_DP || 6) * 2; var validDir = false;
if (isNaN(slopDp)) slopDp = 12; if (rootBackMode === "surface") {
if (slopDp < 12) slopDp = 12; edge = dx >= 0 ? 0 : 1;
validDir = (dx !== 0);
} else {
validDir = (edge === 0 && dx > 0) || (edge === 1 && dx < 0);
}
var shouldIntercept = false;
if (rootBackMode === "surface") {
var commitDp0 = Number(self.config.TOOLAPP_BACK_COMMIT_DISTANCE_DP || 36);
if (isNaN(commitDp0)) commitDp0 = 36;
if (commitDp0 < 1) commitDp0 = 1;
if (commitDp0 > 480) commitDp0 = 480;
var surfaceSlop = self.getToolAppBackSurfaceSlopPx ? self.getToolAppBackSurfaceSlopPx(self.dp(commitDp0)) : Math.min(self.dp(24), self.dp(commitDp0));
var blockedNow = !!(self.isToolAppBackBlockedAt && self.isToolAppBackBlockedAt(this, rootDownRawX, rootDownRawY, dx, dy));
rootBackBlocked = blockedNow;
shouldIntercept = (!blockedNow) && validDir && adx > surfaceSlop && adx > ady * 1.08;
} else {
var slopDp = Number(self.config.CLICK_SLOP_DP || 6);
if (isNaN(slopDp)) slopDp = 6;
if (slopDp < 1) slopDp = 1;
if (slopDp > 40) slopDp = 40; if (slopDp > 40) slopDp = 40;
var touchSlop = self.dp(slopDp); var touchSlop = Math.max(self.dp(8), self.dp(slopDp));
if (validDir && adx > touchSlop && adx > ady * 1.2) { shouldIntercept = validDir && adx > touchSlop && adx > ady * 0.75;
}
if (shouldIntercept) {
rootEdge = edge;
rootBackActive = true; rootBackActive = true;
rootBackMoved = true; rootBackMoved = true;
try { self.prepareToolAppBackPreview(rootEdge); } catch(ePrep) { try { safeLog(self.L, 'w', 'root back preview prepare fail: ' + String(ePrep)); } catch(eLogPrep) {} } try { self.prepareToolAppBackPreview(edge); } catch(ePrep) { try { safeLog(self.L, 'w', 'root back preview prepare fail: ' + String(ePrep)); } catch(eLogPrep) {} }
try { safeLog(self.L, 'd', 'root edge back intercept edge=' + String(rootEdge) + ' dx=' + String(dx)); } catch(eMoveLog) {} try {
var triggerDp0 = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 96);
if (isNaN(triggerDp0)) triggerDp0 = 96;
if (triggerDp0 < 1) triggerDp0 = 1;
if (triggerDp0 > 720) triggerDp0 = 720;
var triggerDistance0 = self.dp(triggerDp0);
var p0 = Math.min(1, adx / triggerDistance0);
self.applyToolAppBackPreviewProgress(edge, p0, adx);
} catch(eFirstMove) {}
try { safeLog(self.L, 'd', 'root back intercept mode=' + String(rootBackMode) + ' edge=' + String(edge) + ' dx=' + String(dx)); } catch(eMoveLog) {}
return true; return true;
} }
return false; return false;
@@ -1261,9 +1486,10 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
var mx = ev.getX() - rootDownX; var mx = ev.getX() - rootDownX;
var my = ev.getY() - rootDownY; var my = ev.getY() - rootDownY;
var validDir2 = (rootEdge === 0 && mx > 0) || (rootEdge === 1 && mx < 0); var validDir2 = (rootEdge === 0 && mx > 0) || (rootEdge === 1 && mx < 0);
if (validDir2 && Math.abs(mx) > Math.abs(my) * 1.2) { var dominance = rootBackMode === "surface" ? 1.08 : 0.75;
var triggerDp = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 180); if (validDir2 && Math.abs(mx) > Math.abs(my) * dominance) {
if (isNaN(triggerDp)) triggerDp = 180; var triggerDp = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 96);
if (isNaN(triggerDp)) triggerDp = 96;
if (triggerDp < 1) triggerDp = 1; if (triggerDp < 1) triggerDp = 1;
if (triggerDp > 720) triggerDp = 720; if (triggerDp > 720) triggerDp = 720;
var triggerDistance = self.dp(triggerDp); var triggerDistance = self.dp(triggerDp);
@@ -1275,15 +1501,18 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
if (action === android.view.MotionEvent.ACTION_UP || action === android.view.MotionEvent.ACTION_CANCEL) { if (action === android.view.MotionEvent.ACTION_UP || action === android.view.MotionEvent.ACTION_CANCEL) {
var ux = ev.getX() - rootDownX; var ux = ev.getX() - rootDownX;
var uy = ev.getY() - rootDownY; var uy = ev.getY() - rootDownY;
var commitDp = Number(self.config.TOOLAPP_BACK_COMMIT_DISTANCE_DP || 72); var commitDp = Number(self.config.TOOLAPP_BACK_COMMIT_DISTANCE_DP || 36);
if (isNaN(commitDp)) commitDp = 72; if (isNaN(commitDp)) commitDp = 36;
if (commitDp < 1) commitDp = 1; if (commitDp < 1) commitDp = 1;
if (commitDp > 480) commitDp = 480; if (commitDp > 480) commitDp = 480;
var completeDistance = self.dp(commitDp); var completeDistance = self.dp(commitDp);
var okDir = (rootEdge === 0 && ux > completeDistance) || (rootEdge === 1 && ux < -completeDistance); var okDir = (rootEdge === 0 && ux > completeDistance) || (rootEdge === 1 && ux < -completeDistance);
var ok = (action === android.view.MotionEvent.ACTION_UP) && rootBackMoved && okDir && Math.abs(ux) > Math.abs(uy) * 1.2; var ratio = rootBackMode === "surface" ? 1.08 : 0.75;
var ok = (action === android.view.MotionEvent.ACTION_UP) && rootBackMoved && okDir && Math.abs(ux) > Math.abs(uy) * ratio;
var edgeDone = rootEdge; var edgeDone = rootEdge;
rootBackActive = false; rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false; rootBackMoved = false;
rootEdge = -1; rootEdge = -1;
self.finishToolAppBackPreview(edgeDone, ok); self.finishToolAppBackPreview(edgeDone, ok);
@@ -1295,6 +1524,8 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
try { safeLog(self.L, 'w', 'tool app root back touch fail: ' + String(e2)); } catch(eLog2) {} try { safeLog(self.L, 'w', 'tool app root back touch fail: ' + String(e2)); } catch(eLog2) {}
} }
rootBackActive = false; rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false; rootBackMoved = false;
rootEdge = -1; rootEdge = -1;
return false; return false;
@@ -1303,7 +1534,7 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
var body = new android.widget.LinearLayout(context); var body = new android.widget.LinearLayout(context);
body.setOrientation(android.widget.LinearLayout.VERTICAL); body.setOrientation(android.widget.LinearLayout.VERTICAL);
// 外层薄荷容器本身就是整张“岛屿设置”卡片:四角统一圆角,并给底部留出完整收口。 // 外层薄荷容器本身就是整张“岛屿设置”卡片:四角统一圆角,并给底部留出完整收口。
body.setPadding(shellPad, shellPad, shellPad, shellPad); body.setPadding(shellPad, shellTopPad, shellPad, shellPad);
body.setBackground(this.ui.createStrokeDrawable(T.bg, this.withAlpha(T.stroke, isDark ? 0.30 : 0.46), this.dp(1), outerRadius)); body.setBackground(this.ui.createStrokeDrawable(T.bg, this.withAlpha(T.stroke, isDark ? 0.30 : 0.46), this.dp(1), outerRadius));
try { body.setClipToOutline(true); } catch(eClip) {} try { body.setClipToOutline(true); } catch(eClip) {}
try { body.setElevation(this.dp((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? 7 : 10)); } catch(eElev) { safeLog(null, 'e', "catch " + String(eElev)); } try { body.setElevation(this.dp((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? 7 : 10)); } catch(eElev) { safeLog(null, 'e', "catch " + String(eElev)); }
@@ -1352,7 +1583,7 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
try { btnClose.setBackground(this.ui.createStrokeDrawable(T.primarySoft, this.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), this.dp(1), this.dp(18))); } catch(eRightBg) {} try { btnClose.setBackground(this.ui.createStrokeDrawable(T.primarySoft, this.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), this.dp(1), this.dp(18))); } catch(eRightBg) {}
bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(104), this.dp(38))); bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(104), this.dp(38)));
var barLp = new android.widget.LinearLayout.LayoutParams(-1, topBarHeight); var barLp = new android.widget.LinearLayout.LayoutParams(-1, topBarHeight);
barLp.setMargins(this.dp(8), this.dp(8), this.dp(8), this.dp(4)); barLp.setMargins(this.dp(8), this.dp(2), this.dp(8), this.dp(4));
body.addView(bar, barLp); body.addView(bar, barLp);
var host = new android.widget.FrameLayout(context); var host = new android.widget.FrameLayout(context);
@@ -1365,13 +1596,6 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
hostLp.setMargins((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), 0, (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6)); hostLp.setMargins((spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), 0, (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6), (spec && (spec.isExpandedWidth || spec.isWideWidth)) ? this.dp(4) : this.dp(6));
body.addView(host, hostLp); body.addView(host, hostLp);
// 兼容旧设置:不再添加页面内透明返回热区。
// 返回手势由 root.onInterceptTouchEvent 按“边缘起手 + 横向阈值 + 方向正确”延迟拦截,避免覆盖控件。
try {
this.state.toolAppInnerBackLeftStrip = null;
this.state.toolAppInnerBackRightStrip = null;
} catch (eStrip) { safeLog(this.L, 'w', "clear edge back strip state fail: " + String(eStrip)); }
this.state.toolAppRoot = root; this.state.toolAppRoot = root;
this.state.toolAppBody = body; this.state.toolAppBody = body;
this.state.toolAppContentHost = host; this.state.toolAppContentHost = host;
@@ -1382,36 +1606,6 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
return root; return root;
}; };
FloatBallAppWM.prototype.updateToolAppInnerBackEdgeWidth = function() {
try {
var w = this.getToolAppBackEdgeWidthPx ? this.getToolAppBackEdgeWidthPx() : this.dp(48);
var left = this.state.toolAppInnerBackLeftStrip;
var right = this.state.toolAppInnerBackRightStrip;
if (left) {
var lpL = left.getLayoutParams();
if (lpL) {
lpL.width = w;
left.setLayoutParams(lpL);
}
}
if (right) {
var lpR = right.getLayoutParams();
if (lpR) {
lpR.width = w;
right.setLayoutParams(lpR);
}
}
return true;
} catch(e) {
safeLog(this.L, "w", "update inner back edge width fail: " + String(e));
}
return false;
};
FloatBallAppWM.prototype.ensureToolAppShell = function() { FloatBallAppWM.prototype.ensureToolAppShell = function() {
try { try {
if (this.state.toolAppRoot && this.state.toolAppContentHost) return this.state.toolAppRoot; if (this.state.toolAppRoot && this.state.toolAppContentHost) return this.state.toolAppRoot;
@@ -1451,6 +1645,11 @@ FloatBallAppWM.prototype.setToolAppContent = function(contentView) {
try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); } try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); }
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); } try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); }
host.addView(contentView, new android.widget.FrameLayout.LayoutParams(-1, -1)); host.addView(contentView, new android.widget.FrameLayout.LayoutParams(-1, -1));
try {
var st = this.state.toolAppNavStack || [];
var top = st.length ? st[st.length - 1] : null;
if (this.restoreToolAppScrollLater) this.restoreToolAppScrollLater(contentView, top, true);
} catch(eRestoreScroll) {}
return true; return true;
} catch (e) { } catch (e) {
safeLog(this.L, 'e', "setToolAppContent fail: " + String(e)); safeLog(this.L, 'e', "setToolAppContent fail: " + String(e));
@@ -1458,6 +1657,23 @@ FloatBallAppWM.prototype.setToolAppContent = function(contentView) {
return false; return false;
}; };
FloatBallAppWM.prototype.getToolAppStatusBarInsetPx = function() {
var inset = 0;
try {
var res = context.getResources();
var id = res.getIdentifier("status_bar_height", "dimen", "android");
if (id > 0) inset = Number(res.getDimensionPixelSize(id) || 0);
} catch(eRes) {
inset = 0;
}
if (isNaN(inset) || inset < 0) inset = 0;
try {
var maxInset = this.dp(48);
if (inset > maxInset) inset = maxInset;
} catch(eClamp) {}
return Math.floor(inset);
};
FloatBallAppWM.prototype.calculateToolAppLayout = function(shell) { FloatBallAppWM.prototype.calculateToolAppLayout = function(shell) {
var sw = Math.max(1, Number(this.state.screen && this.state.screen.w || 0)); var sw = Math.max(1, Number(this.state.screen && this.state.screen.w || 0));
var sh = Math.max(1, Number(this.state.screen && this.state.screen.h || 0)); var sh = Math.max(1, Number(this.state.screen && this.state.screen.h || 0));
@@ -1468,27 +1684,30 @@ FloatBallAppWM.prototype.calculateToolAppLayout = function(shell) {
var isLandscape = spec ? spec.isLandscape : (sw > sh); var isLandscape = spec ? spec.isLandscape : (sw > sh);
var shortSide = Math.min(sw, sh); var shortSide = Math.min(sw, sh);
var longSide = Math.max(sw, sh); var longSide = Math.max(sw, sh);
var marginX = this.dp(12), marginTop = this.dp(14), marginBottom = this.dp(14); var marginX = this.dp(12), marginTop = 0, marginBottom = this.dp(14);
var statusInset = 0;
try { statusInset = this.getToolAppStatusBarInsetPx ? this.getToolAppStatusBarInsetPx() : 0; } catch(eInset) { statusInset = 0; }
if (isNaN(statusInset) || statusInset < 0) statusInset = 0;
var targetW, targetH; var targetW, targetH;
if (spec && (spec.isCompactWidth || shortSide < this.dp(420))) { if (spec && (spec.isCompactWidth || shortSide < this.dp(420))) {
marginX = this.dp(isLandscape ? 8 : 10); marginTop = this.dp(isLandscape ? 6 : 14); marginBottom = this.dp(isLandscape ? 6 : 14); marginX = this.dp(isLandscape ? 8 : 10); marginTop = 0; marginBottom = this.dp(isLandscape ? 6 : 14);
targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2); targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2);
targetH = isLandscape ? (sh - marginTop - marginBottom) : Math.min(Math.floor(sh * 0.92), sh - marginTop - marginBottom); targetH = isLandscape ? (sh - marginTop - marginBottom) : Math.min(Math.floor(sh * 0.96), sh - marginTop - marginBottom);
} else if (spec && spec.isMediumWidth) { } else if (spec && spec.isMediumWidth) {
marginX = this.dp(18); marginTop = this.dp(isLandscape ? 10 : 18); marginBottom = this.dp(isLandscape ? 10 : 18); marginX = this.dp(18); marginTop = 0; marginBottom = this.dp(isLandscape ? 10 : 18);
targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2); targetH = sh - marginTop - marginBottom; targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2); targetH = sh - marginTop - marginBottom;
} else if (spec && (spec.isExpandedWidth || shortSide >= this.dp(720))) { } else if (spec && (spec.isExpandedWidth || shortSide >= this.dp(720))) {
marginX = this.dp(22); marginTop = this.dp(isLandscape ? 18 : 24); marginBottom = this.dp(isLandscape ? 18 : 24); marginX = this.dp(22); marginTop = 0; marginBottom = this.dp(isLandscape ? 18 : 24);
targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2); targetH = sh - marginTop - marginBottom; targetW = Math.min(spec.contentMaxWidth, sw - marginX * 2); targetH = sh - marginTop - marginBottom;
} else { } else {
marginX = this.dp(30); marginTop = this.dp(24); marginBottom = this.dp(24); marginX = this.dp(30); marginTop = 0; marginBottom = this.dp(24);
targetW = Math.min(spec ? spec.contentMaxWidth : this.dp(1080), sw - marginX * 2); targetW = Math.min(spec ? spec.contentMaxWidth : this.dp(1080), sw - marginX * 2);
targetH = Math.min(this.dp(860), sh - marginTop - marginBottom); targetH = Math.min(this.dp(900), sh - marginTop - marginBottom);
} }
targetW = Math.max(this.dp(300), Math.min(targetW, sw - marginX * 2)); targetW = Math.max(this.dp(300), Math.min(targetW, sw - marginX * 2));
targetH = Math.max(this.dp(320), Math.min(targetH, sh - marginTop - marginBottom)); targetH = Math.max(this.dp(320), Math.min(targetH, sh - marginTop - marginBottom));
var x = Math.floor((sw - targetW) / 2); var x = Math.floor((sw - targetW) / 2);
var y = Math.floor((sh - targetH) / 2); var y = marginTop;
if (x < marginX) x = marginX; if (x < marginX) x = marginX;
if (y < marginTop) y = marginTop; if (y < marginTop) y = marginTop;
if (x + targetW > sw - marginX) x = Math.max(0, sw - marginX - targetW); if (x + targetW > sw - marginX) x = Math.max(0, sw - marginX - targetW);
@@ -1512,9 +1731,11 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
this.state.pendingUserCfg = null; this.state.pendingUserCfg = null;
this.state.pendingDirty = false; this.state.pendingDirty = false;
this.state.previewMode = false; this.state.previewMode = false;
this.state.toolAppScrollY = 0;
} }
if (resetStack || !this.state.toolAppNavStack || !this.state.toolAppNavStack.length) { if (resetStack || !this.state.toolAppNavStack || !this.state.toolAppNavStack.length) {
this.state.toolAppNavStack = [this.makeToolAppStackEntry(r)]; this.state.toolAppNavStack = [this.makeToolAppStackEntry(r)];
try { this.bumpToolAppStackVersion(); } catch(eStackInit) {}
} }
var raw = this.buildPanelView(r); var raw = this.buildPanelView(r);
@@ -1543,7 +1764,6 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
} catch (eUpd) { safeLog(this.L, 'w', "tool_app update layout fail: " + String(eUpd)); } } catch (eUpd) { safeLog(this.L, 'w', "tool_app update layout fail: " + String(eUpd)); }
try { shell.requestFocus(); } catch (eFocus) {} try { shell.requestFocus(); } catch (eFocus) {}
} }
try { this.refreshToolAppScreenBackStrips(); } catch (eScreenBack) { safeLog(this.L, 'w', "screen edge back strip update fail: " + String(eScreenBack)); }
} catch (e) { } catch (e) {
this.state.toolAppActive = false; this.state.toolAppActive = false;
safeLog(this.L, 'e', "showToolApp fail route=" + r + " err=" + String(e)); safeLog(this.L, 'e', "showToolApp fail route=" + r + " err=" + String(e));
@@ -1553,12 +1773,14 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
FloatBallAppWM.prototype.pushToolAppPage = function(route) { FloatBallAppWM.prototype.pushToolAppPage = function(route) {
if (!this.isToolAppRoute(route)) return; if (!this.isToolAppRoute(route)) return;
try { if (this.saveToolAppCurrentStackScroll) this.saveToolAppCurrentStackScroll(); } catch(eSaveScrollPush) {}
if (!this.state.toolAppNavStack) this.state.toolAppNavStack = []; if (!this.state.toolAppNavStack) this.state.toolAppNavStack = [];
if (this.state.toolAppNavStack.length <= 0) { if (this.state.toolAppNavStack.length <= 0) {
this.state.toolAppNavStack.push(this.makeToolAppStackEntry(this.state.toolAppRoute || "settings")); this.state.toolAppNavStack.push(this.makeToolAppStackEntry(this.state.toolAppRoute || "settings"));
} }
var nextEntry = this.makeToolAppStackEntry(route); var nextEntry = this.cloneToolAppPageSnapshot ? this.cloneToolAppPageSnapshot(this.makeToolAppStackEntry(route)) : this.makeToolAppStackEntry(route);
this.state.toolAppNavStack.push(nextEntry); this.state.toolAppNavStack.push(nextEntry);
try { this.bumpToolAppStackVersion(); } catch(eStackPush) {}
if (this.applyToolAppPageSnapshot) this.applyToolAppPageSnapshot(nextEntry); if (this.applyToolAppPageSnapshot) this.applyToolAppPageSnapshot(nextEntry);
this.showToolApp(route, false); this.showToolApp(route, false);
}; };
@@ -1573,6 +1795,7 @@ FloatBallAppWM.prototype.replaceToolAppPage = function(route) {
var entry = this.makeToolAppStackEntry(route); var entry = this.makeToolAppStackEntry(route);
if (!this.state.toolAppNavStack || !this.state.toolAppNavStack.length) this.state.toolAppNavStack = [entry]; if (!this.state.toolAppNavStack || !this.state.toolAppNavStack.length) this.state.toolAppNavStack = [entry];
else this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = entry; else this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = entry;
try { this.bumpToolAppStackVersion(); } catch(eStackReplace) {}
if (this.applyToolAppPageSnapshot) this.applyToolAppPageSnapshot(entry); if (this.applyToolAppPageSnapshot) this.applyToolAppPageSnapshot(entry);
this.showToolApp(route, false); this.showToolApp(route, false);
}; };
@@ -1586,6 +1809,7 @@ FloatBallAppWM.prototype.popToolAppPage = function(reason) {
this.state.settingsHomeSelectedItemId = null; this.state.settingsHomeSelectedItemId = null;
if (this.state.toolAppNavStack && this.state.toolAppNavStack.length > 0) { if (this.state.toolAppNavStack && this.state.toolAppNavStack.length > 0) {
this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = this.makeToolAppStackEntry("settings"); this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = this.makeToolAppStackEntry("settings");
try { this.bumpToolAppStackVersion(); } catch(eStackPane) {}
} }
this.showToolApp("settings", false); this.showToolApp("settings", false);
return true; return true;
@@ -1596,6 +1820,7 @@ FloatBallAppWM.prototype.popToolAppPage = function(reason) {
return true; return true;
} }
this.state.toolAppNavStack.pop(); this.state.toolAppNavStack.pop();
try { this.bumpToolAppStackVersion(); } catch(eStackPop) {}
var top = this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1]; var top = this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1];
var target = this.cloneToolAppPageSnapshot ? this.cloneToolAppPageSnapshot(top) : top; var target = this.cloneToolAppPageSnapshot ? this.cloneToolAppPageSnapshot(top) : top;
var nextRoute = target && target.route ? String(target.route) : "settings"; var nextRoute = target && target.route ? String(target.route) : "settings";

View File

@@ -128,17 +128,6 @@ FloatBallAppWM.prototype.close = function() {
safeLog(this.L, 'i', "close done"); safeLog(this.L, 'i', "close done");
// # 清理日志定时器
try {
if (this.L) {
try { this.L._flushBuffer(); } catch (eFlushLog0) { safeLog(this.L, 'e', "logger flush fail: " + String(eFlushLog0)); }
if (this.L._flushTimer) {
this.L._flushTimer.cancel();
this.L._flushTimer = null;
}
}
} catch (eLog) {}
// # 清空缓存 // # 清空缓存
try { try {
this._iconLru = null; this._iconLru = null;

View File

@@ -2,12 +2,12 @@
"alg": "SHA256withRSA", "alg": "SHA256withRSA",
"files": { "files": {
"th_01_base.js": { "th_01_base.js": {
"sha256": "f228e3fda85ab9946934a9950515556d75df110da88432b339258036b8f3a3e2", "sha256": "e465a92b21f6819079255719bfe41c28a42ff3716f1d29b399897bc299ef92ec",
"size": 57166 "size": 54633
}, },
"th_02_core.js": { "th_02_core.js": {
"sha256": "3c5c498d200e961d48fc9ca3f885475e770ecb32b83ec6a89d23df3f88aed1c9", "sha256": "7bfd21df21c137595c3e2a8724b3eb3f0cce82ef0dd18b732de08e9be30b2ba3",
"size": 4664 "size": 4631
}, },
"th_03_icon.js": { "th_03_icon.js": {
"sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987", "sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987",
@@ -18,8 +18,8 @@
"size": 42568 "size": 42568
}, },
"th_05_persistence.js": { "th_05_persistence.js": {
"sha256": "1bec9f5bda8e5ae82f2a42707c59e72f4819fffc200d74ae55f191a76009af9b", "sha256": "7fd2a62275bd26fcd940202057480fca7a8a800eaeb1fcb9d52003e255d1ef60",
"size": 15754 "size": 14763
}, },
"th_06_icon_parser.js": { "th_06_icon_parser.js": {
"sha256": "25b95a5df634a7ee359f3ab798e4d3154a71c24016f7b4bf8a658096644b2484", "sha256": "25b95a5df634a7ee359f3ab798e4d3154a71c24016f7b4bf8a658096644b2484",
@@ -34,16 +34,16 @@
"size": 7938 "size": 7938
}, },
"th_09_animation.js": { "th_09_animation.js": {
"sha256": "2570ffbbc33fa2318e27c5e99c4e78e1bf96c7da8ec134053acda009207488cb", "sha256": "7c4b673869978f9015b8641bd2266fc7bbd2131f96338b4bb9c12fab11c50468",
"size": 42007 "size": 41346
}, },
"th_10_shell.js": { "th_10_shell.js": {
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc", "sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
"size": 1094 "size": 1094
}, },
"th_11_action.js": { "th_11_action.js": {
"sha256": "a0142d26621f3d076bd1b749f2885af2c0806c9f206e362a3b3680a5d2312b31", "sha256": "1ba348415e891093bcf49619d950e9653e1c7323c3d35f74f6f71bec14b7c9e6",
"size": 13545 "size": 13838
}, },
"th_12_rebuild.js": { "th_12_rebuild.js": {
"sha256": "7b820e813d2dd8866778fefe8bfeb6aca227bb1a32a89d318de830178f19824f", "sha256": "7b820e813d2dd8866778fefe8bfeb6aca227bb1a32a89d318de830178f19824f",
@@ -53,20 +53,32 @@
"sha256": "9052e01ccdc0af29615ef8e72550d7d312240f9ab36ebfb66e673c92ab2e4b9c", "sha256": "9052e01ccdc0af29615ef8e72550d7d312240f9ab36ebfb66e673c92ab2e4b9c",
"size": 22308 "size": 22308
}, },
"th_14_color_picker.js": {
"sha256": "2f6378fbb718d4b31cf11d3047cee6b32af93cfbb932442da29dd05225881432",
"size": 23870
},
"th_14_icon_picker.js": {
"sha256": "1df5c4bf82e68de24d7b89c852a4d9878768451effe086e8ddb86620ecc98c35",
"size": 23906
},
"th_14_panels.js": { "th_14_panels.js": {
"sha256": "4b73af93ff5b8fa8ffadf76f936732b93a32ccff8a6623b96f9ad3bbb8114ec8", "sha256": "33ed77006c3857a9eb93a72363a557ad87c425531e9d9d88eebc4bc8b4b28065",
"size": 304993 "size": 276124
},
"th_14_schema_editor.js": {
"sha256": "5669d0b5a16f770bed24eedee24203df57f7cbc7910c840931e533adac1ef146",
"size": 20484
}, },
"th_15_extra.js": { "th_15_extra.js": {
"sha256": "fb8349a61771903a8c2a3ca6fee15f8df88af79b684e7b444b35c38bcf354ed5", "sha256": "2c4e560b793ee54f9338cc44bd41a056cbf8688560f9494920322c88d04be219",
"size": 117741 "size": 128697
}, },
"th_16_entry.js": { "th_16_entry.js": {
"sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1", "sha256": "652aa70214a9419923785e528a067d3828094fde48fc9c8c57cfda1e08206e25",
"size": 12799 "size": 12479
} }
}, },
"keyId": "toolhub-targets-2026-rsa3072", "keyId": "toolhub-targets-2026-rsa3072",
"schema": 2, "schema": 2,
"version": 20260521235402 "version": 20260522222851
} }

View File

@@ -1 +1 @@
Ho5g8n2gFgHoMdeo0wpzG4b3ufGTSs7Ht1MbGOGU0h0r/u3QRAJKJTCf1JPmSb6mWCOdM8ZL+LD0u8dfpJd9f4AdIJm+PNHH60YGa6QFEAuzh13eh1wQrd2S/gTSQGMSFqQPKRhdMzuTTBJEgMGK0S3siZP6RPK0hdocs5EpFnAtNd6HZWglcds1FVHMCwYiT1JCffRl+azDDaYlo5JqfdPz6hxfQkw+MEY5hQJ0veSbpuv5wNWRZ1OaPgIdwu7BMnGjbCR8BbYylXBkbB0K2K8FKeLDvuPY9je64xSgN1+dJpV1QTr+52BwxDH53u0L7ZVMYOJwOpdtLNhHNVjKJFJn3vcn2jS0DrX6uP9w/qI9X7Ss2bn8Xf/bkaMj6UpOOB+arkme8P2bM+ziGA/CL2n7SLsicvbUv3eNWRsjwTw9EIUx/sEqo6tr+m88eorZo7C3Idy5gdCtVjSSN3kef1XbHtOQshBZwXYpmFYPtRS0WxqEF7F/desj7k7ztnHv Wt5bWVBi/YnfXYbb1pEQNWYaD0nbZ6QqW1NKp791UCjKV+Qlen6Tt9NNDYCDYwITODZAp3FPJfAzVkKawUX/ROh9u81UExKBvXGJJ5JQT9PwbrbP1JJ3oPV3CDFUTfwMCc7ev2c5R6hL+29CXEksRYXH+bqGW9fTvOAYeHsa8rRZTPXyAvH2f36XTuc7wXHvKpwLjmPZt68Ep4ICEJFi+wHZPS3r2qljsYFUmbSRCRPuv/E4pqVIz33vWaF3gepQgeNVAjnJ8c2dmQhjKiZ0wIOmOQqwNbDXjNYZNUrXoaPzJV/6OIzJRK8MyDWQbiWBwZwm3CJCZKfdI1qR1TGScInWOBBGhJ7IDilElGTHd6wwQsw4Y4h6KBqLaWT5bzxp2x+vRTXYYdc6RGR0yaauWVTqCCE2zf4Z6IfRNht/2+BZ4VG4W7WhEN72zdbPihfsJcxPUjQklN5gp3PScEI9PwymlecsDHOHmDSYFER3TUai3Uds7DQiTYjVxSTpce0f

View File

@@ -28,7 +28,8 @@ 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_14_color_picker.js", "th_14_icon_picker.js",
"th_14_schema_editor.js", "th_15_extra.js", "th_16_entry.js",
] ]